webforai 1.5.0 → 1.6.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/dist/bin.js +972 -213
- package/dist/cjs/cli/bin.js +33 -0
- package/dist/cjs/cli/commands/webforai/index.js +86 -0
- package/dist/cjs/cli/commands/webforai/loadHtml.js +93 -0
- package/dist/cjs/cli/constants.js +34 -0
- package/dist/cjs/cli/helpers/assertContinue.js +34 -0
- package/dist/cjs/cli/helpers/inputOutputPath.js +89 -0
- package/dist/cjs/cli/helpers/inputSourcePath.js +64 -0
- package/dist/cjs/cli/helpers/selectExtractMode.js +39 -0
- package/dist/cjs/cli/helpers/selectLoader.js +44 -0
- package/dist/cjs/cli/utils.js +89 -0
- package/dist/cjs/extract-hast/index.js +4 -3
- package/dist/cjs/extract-hast/readability.js +28 -61
- package/dist/cjs/extract-hast/strongly.js +2 -1
- package/dist/cjs/extract-hast/utils.js +0 -15
- package/dist/cjs/html-to-mdast.js +6 -6
- package/dist/cjs/link-replacer.js +10 -5
- package/dist/cjs/loaders/playwright.js +36 -7
- package/dist/cjs/mdast-handlers/custom-br-handler.js +31 -0
- package/dist/cjs/mdast-to-markdown.js +1 -1
- package/dist/cjs/utils/hast-utils.js +71 -0
- package/dist/cli/bin.js +11 -0
- package/dist/cli/commands/webforai/index.js +53 -0
- package/dist/cli/commands/webforai/loadHtml.js +60 -0
- package/dist/cli/constants.js +9 -0
- package/dist/cli/helpers/assertContinue.js +11 -0
- package/dist/cli/helpers/inputOutputPath.js +56 -0
- package/dist/cli/helpers/inputSourcePath.js +31 -0
- package/dist/cli/helpers/selectExtractMode.js +16 -0
- package/dist/cli/helpers/selectLoader.js +21 -0
- package/dist/cli/utils.js +52 -0
- package/dist/extract-hast/index.js +4 -3
- package/dist/extract-hast/readability.js +29 -62
- package/dist/extract-hast/strongly.js +2 -1
- package/dist/extract-hast/utils.js +0 -14
- package/dist/html-to-mdast.js +6 -6
- package/dist/link-replacer.js +10 -5
- package/dist/loaders/playwright.js +36 -7
- package/dist/mdast-handlers/custom-br-handler.js +8 -0
- package/dist/mdast-to-markdown.js +1 -1
- package/dist/types/cli/bin.js.map +1 -0
- package/dist/types/cli/commands/webforai/index.d.ts +6 -0
- package/dist/types/cli/commands/webforai/index.js.map +1 -0
- package/dist/types/cli/commands/webforai/loadHtml.d.ts +3 -0
- package/dist/types/cli/commands/webforai/loadHtml.js.map +1 -0
- package/dist/types/cli/constants.d.ts +5 -0
- package/dist/types/cli/constants.js.map +1 -0
- package/dist/types/cli/helpers/assertContinue.d.ts +1 -0
- package/dist/types/cli/helpers/assertContinue.js.map +1 -0
- package/dist/types/cli/helpers/inputOutputPath.d.ts +1 -0
- package/dist/types/cli/helpers/inputOutputPath.js.map +1 -0
- package/dist/types/cli/helpers/inputSourcePath.d.ts +1 -0
- package/dist/types/cli/helpers/inputSourcePath.js.map +1 -0
- package/dist/types/cli/helpers/selectExtractMode.d.ts +1 -0
- package/dist/types/cli/helpers/selectExtractMode.js.map +1 -0
- package/dist/types/cli/helpers/selectLoader.d.ts +1 -0
- package/dist/types/cli/helpers/selectLoader.js.map +1 -0
- package/dist/types/cli/utils.d.ts +32 -0
- package/dist/types/cli/utils.js.map +1 -0
- package/dist/types/extract-hast/index.d.ts +9 -4
- package/dist/types/extract-hast/index.js.map +1 -1
- package/dist/types/extract-hast/readability.d.ts +2 -1
- package/dist/types/extract-hast/readability.js.map +1 -1
- package/dist/types/extract-hast/strongly.d.ts +2 -1
- package/dist/types/extract-hast/strongly.js.map +1 -1
- package/dist/types/extract-hast/utils.d.ts +0 -1
- package/dist/types/extract-hast/utils.js.map +1 -1
- package/dist/types/html-to-mdast.d.ts +4 -0
- package/dist/types/html-to-mdast.js.map +1 -1
- package/dist/types/link-replacer.d.ts +1 -1
- package/dist/types/link-replacer.js.map +1 -1
- package/dist/types/loaders/playwright.d.ts +8 -2
- package/dist/types/loaders/playwright.js.map +1 -1
- package/dist/types/mdast-handlers/custom-br-handler.d.ts +2 -0
- package/dist/types/mdast-handlers/custom-br-handler.js.map +1 -0
- package/dist/types/mdast-to-markdown.js.map +1 -1
- package/dist/types/utils/hast-utils.d.ts +4 -0
- package/dist/types/utils/hast-utils.js.map +1 -0
- package/dist/utils/hast-utils.js +46 -0
- package/package.json +18 -17
- package/dist/cjs/bin.js +0 -254
- package/dist/cjs/html-to-markdown.test.js +0 -146
- package/dist/cjs/link-replacer.test.js +0 -47
- package/dist/cjs/loaders/fetch.test.js +0 -9
- package/dist/cjs/loaders/playwright.test.js +0 -17
- package/dist/cjs/loaders/puppeteer.test.js +0 -17
- package/dist/cjs/utils/bin-utils.js +0 -124
- package/dist/html-to-markdown.test.js +0 -124
- package/dist/link-replacer.test.js +0 -47
- package/dist/loaders/fetch.test.js +0 -9
- package/dist/loaders/playwright.test.js +0 -17
- package/dist/loaders/puppeteer.test.js +0 -17
- package/dist/types/bin.js.map +0 -1
- package/dist/types/utils/bin-utils.d.ts +0 -5
- package/dist/types/utils/bin-utils.js.map +0 -1
- package/dist/utils/bin-utils.js +0 -87
- /package/dist/types/{bin.d.ts → cli/bin.d.ts} +0 -0
|
@@ -30,35 +30,24 @@ const REGEXPS = {
|
|
|
30
30
|
hidden: /hidden|invisible|fallback-image/i,
|
|
31
31
|
byline: /byline|author|dateline|writtenby|p-author/i,
|
|
32
32
|
specialUnlikelyCandidates: /frb-|uls-menu|language-link/i,
|
|
33
|
-
unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote|speechify-ignore/i,
|
|
33
|
+
unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|tooltip|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote|speechify-ignore|avatar/i,
|
|
34
34
|
okMaybeItsaCandidate: /and|article|body|column|content|main|shadow|code/i
|
|
35
35
|
};
|
|
36
|
-
const BODY_SELECTORS = ["article", "#article", ".article_body", ".article-body", "#content", ".entry"
|
|
37
|
-
const PARAGRAPH_TAGS = ["p", "div", "section", "article", "main", "ul", "ol", "li"];
|
|
36
|
+
const BODY_SELECTORS = ["article", "#article", ".article_body", ".article-body", "#content", ".entry"];
|
|
37
|
+
const PARAGRAPH_TAGS = ["a", "p", "div", "section", "article", "main", "ul", "ol", "li"];
|
|
38
38
|
const BASE_MINIMAL_LENGTH = {
|
|
39
39
|
ja: 200,
|
|
40
40
|
en: 500
|
|
41
41
|
};
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
const element = node;
|
|
47
|
-
if (element.tagName !== "html") {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const langAttr = element.properties.lang || element.properties["xml:lang"];
|
|
51
|
-
if (langAttr) {
|
|
52
|
-
return langAttr;
|
|
53
|
-
}
|
|
42
|
+
const metadataFilter = (node) => {
|
|
43
|
+
return !(["comment", "doctype"].includes(node.type) || node.type === "element" && ["script", "style", "link", "meta", "noscript", "svg", "title"].includes(node.tagName));
|
|
54
44
|
};
|
|
55
|
-
const metadataFilter = (node) => !["comment", "doctype"].includes(node.type);
|
|
56
45
|
const universalElementFilter = (node) => {
|
|
57
46
|
if (node.type !== "element") {
|
|
58
47
|
return true;
|
|
59
48
|
}
|
|
60
49
|
const element = node;
|
|
61
|
-
if (["
|
|
50
|
+
if (["aside", "nav"].includes(element.tagName)) {
|
|
62
51
|
return false;
|
|
63
52
|
}
|
|
64
53
|
if (["hidden", "aria-hidden"].some((key) => element.properties[key])) {
|
|
@@ -89,10 +78,7 @@ const unlikelyElementFilter = (node) => {
|
|
|
89
78
|
return true;
|
|
90
79
|
}
|
|
91
80
|
const element = node;
|
|
92
|
-
if (["body", "article", "main", "section", "
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
if ((0, import_utils.hasAncestors)(element, ["table", "code"], 3)) {
|
|
81
|
+
if (["body", "article", "main", "section", "h1", "h2", "h3", "h4", "h5", "h6", "p"].includes(element.tagName)) {
|
|
96
82
|
return true;
|
|
97
83
|
}
|
|
98
84
|
const match = (0, import_utils.matchString)(element);
|
|
@@ -104,55 +90,36 @@ const unlikelyElementFilter = (node) => {
|
|
|
104
90
|
}
|
|
105
91
|
return true;
|
|
106
92
|
};
|
|
107
|
-
const
|
|
108
|
-
const a = (0, import_hast_util_select.select)("a", element);
|
|
109
|
-
const img = (0, import_hast_util_select.select)("img", a);
|
|
110
|
-
if (!(a && img)) {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
const imgFilename = img.properties.src?.toString().split("/").pop();
|
|
114
|
-
const hrefFilename = a.properties.href?.toString().split("/").pop();
|
|
115
|
-
return imgFilename === hrefFilename && imgFilename;
|
|
116
|
-
};
|
|
117
|
-
const removeEmptyFilter = (node) => {
|
|
93
|
+
const removeEmptyFilter = (node, _lang) => {
|
|
118
94
|
if (node.type !== "element") {
|
|
119
95
|
return true;
|
|
120
96
|
}
|
|
121
97
|
const element = node;
|
|
122
|
-
if (
|
|
98
|
+
if (PARAGRAPH_TAGS.includes(element.tagName)) {
|
|
123
99
|
return true;
|
|
124
100
|
}
|
|
125
|
-
if (
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
128
|
-
const text = (0, import_hast_util_to_string.toString)(element);
|
|
129
|
-
if (text.length < 10) {
|
|
101
|
+
if (element.tagName === "img" && !element.properties.src) {
|
|
130
102
|
return false;
|
|
131
103
|
}
|
|
132
104
|
return true;
|
|
133
105
|
};
|
|
134
|
-
const readabilityExtractHast = (
|
|
135
|
-
const lang =
|
|
106
|
+
const readabilityExtractHast = (params) => {
|
|
107
|
+
const { hast, lang = "en" } = params;
|
|
136
108
|
const body = (0, import_hast_util_select.select)("body", hast) ?? hast;
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return true;
|
|
146
|
-
});
|
|
147
|
-
if (!baseFilterd) {
|
|
148
|
-
return { type: "root", children: [] };
|
|
149
|
-
}
|
|
150
|
-
const baseFilterdText = (0, import_hast_util_to_string.toString)(baseFilterd);
|
|
151
|
-
let [baseTree, baseText] = baseFilterdText.length > bodyText.length / 3 || baseFilterdText.length > 5e3 ? [baseFilterd, baseFilterdText] : [body, bodyText];
|
|
109
|
+
const metadataFilteredHast = (0, import_unist_util_filter.filter)(body, (node) => metadataFilter(node));
|
|
110
|
+
const metadataFilteredHastText = metadataFilteredHast && (0, import_hast_util_to_string.toString)(metadataFilteredHast);
|
|
111
|
+
if (!(metadataFilteredHast && metadataFilteredHastText)) {
|
|
112
|
+
return body;
|
|
113
|
+
}
|
|
114
|
+
const baseFilterd = (0, import_unist_util_filter.filter)(metadataFilteredHast, (node) => universalElementFilter(node));
|
|
115
|
+
const baseFilterdText = baseFilterd ? (0, import_hast_util_to_string.toString)(baseFilterd) : "";
|
|
116
|
+
const [baseTree, baseText] = baseFilterdText.length > metadataFilteredHastText.length / 3 || baseFilterdText.length > 5e3 ? [baseFilterd, baseFilterdText] : [metadataFilteredHast, metadataFilteredHastText];
|
|
152
117
|
let minimalLength = lang in BASE_MINIMAL_LENGTH ? BASE_MINIMAL_LENGTH[lang] : 500;
|
|
153
118
|
if (baseText.length < minimalLength) {
|
|
154
119
|
minimalLength = Math.max(0, baseText.length - 200);
|
|
155
120
|
}
|
|
121
|
+
let extractedTree = baseTree;
|
|
122
|
+
let extractedText = baseText;
|
|
156
123
|
for (const selector of BODY_SELECTORS) {
|
|
157
124
|
const content = { type: "root", children: (0, import_hast_util_select.selectAll)(selector, baseFilterd) };
|
|
158
125
|
const contentText = (0, import_hast_util_to_string.toString)(content);
|
|
@@ -161,18 +128,18 @@ const readabilityExtractHast = (hast) => {
|
|
|
161
128
|
}
|
|
162
129
|
const links = (0, import_hast_util_select.selectAll)("a", content);
|
|
163
130
|
const linkText = links.map((link) => (0, import_hast_util_to_string.toString)(link)).join("");
|
|
164
|
-
const linkDensity = linkText.length /
|
|
131
|
+
const linkDensity = linkText.length / contentText.length;
|
|
165
132
|
if (linkDensity > 0.4) {
|
|
166
133
|
continue;
|
|
167
134
|
}
|
|
168
135
|
if (contentText.length > minimalLength) {
|
|
169
|
-
|
|
170
|
-
|
|
136
|
+
extractedTree = content;
|
|
137
|
+
extractedText = contentText;
|
|
171
138
|
break;
|
|
172
139
|
}
|
|
173
140
|
}
|
|
174
|
-
const finalFilteredTree = (0, import_unist_util_filter.filter)(
|
|
175
|
-
if (!removeEmptyFilter(node)) {
|
|
141
|
+
const finalFilteredTree = (0, import_unist_util_filter.filter)(extractedTree, (node) => {
|
|
142
|
+
if (!removeEmptyFilter(node, lang)) {
|
|
176
143
|
return false;
|
|
177
144
|
}
|
|
178
145
|
if (!unlikelyElementFilter(node)) {
|
|
@@ -180,7 +147,7 @@ const readabilityExtractHast = (hast) => {
|
|
|
180
147
|
}
|
|
181
148
|
return true;
|
|
182
149
|
});
|
|
183
|
-
const finalTree = (0, import_hast_util_to_string.toString)(finalFilteredTree).length >
|
|
150
|
+
const finalTree = (0, import_hast_util_to_string.toString)(finalFilteredTree).length > extractedText.length / 3 ? finalFilteredTree : extractedTree;
|
|
184
151
|
return finalTree;
|
|
185
152
|
};
|
|
186
153
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -24,7 +24,8 @@ module.exports = __toCommonJS(strongly_exports);
|
|
|
24
24
|
var import_hast_util_select = require("hast-util-select");
|
|
25
25
|
var import_unist_util_filter = require("unist-util-filter");
|
|
26
26
|
const REMOVE_REGEX = /fallback|icon|header|nav|speechify-ignore|assist|-ad-|banner|combx|comment|com-|contact|foot|footer|footnote|gdpr|masthead|media|meta|outbrain|promo|related|scroll|share|assist|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i;
|
|
27
|
-
const stronglyExtractHast = (
|
|
27
|
+
const stronglyExtractHast = (params) => {
|
|
28
|
+
const { hast } = params;
|
|
28
29
|
const selectors = ["article", "#article", "#content", "main", "div.container"];
|
|
29
30
|
let selectedElement = hast;
|
|
30
31
|
for (const selector of selectors) {
|
|
@@ -19,7 +19,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
var utils_exports = {};
|
|
20
20
|
__export(utils_exports, {
|
|
21
21
|
classnames: () => classnames,
|
|
22
|
-
hasAncestors: () => hasAncestors,
|
|
23
22
|
isStrInclude: () => isStrInclude,
|
|
24
23
|
matchString: () => matchString
|
|
25
24
|
});
|
|
@@ -37,23 +36,9 @@ const isStrInclude = (value, match) => {
|
|
|
37
36
|
}
|
|
38
37
|
return false;
|
|
39
38
|
};
|
|
40
|
-
const hasAncestors = (element, tagNames, depth = 3) => {
|
|
41
|
-
let parent = element.parent;
|
|
42
|
-
for (let i = 0; i < depth; i++) {
|
|
43
|
-
if (!parent || parent.type !== "element") {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
if (tagNames.includes(parent.tagName)) {
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
parent = parent.parent;
|
|
50
|
-
}
|
|
51
|
-
return false;
|
|
52
|
-
};
|
|
53
39
|
// Annotate the CommonJS export names for ESM import in node:
|
|
54
40
|
0 && (module.exports = {
|
|
55
41
|
classnames,
|
|
56
|
-
hasAncestors,
|
|
57
42
|
isStrInclude,
|
|
58
43
|
matchString
|
|
59
44
|
});
|
|
@@ -30,12 +30,13 @@ var import_custom_code_handler = require("./mdast-handlers/custom-code-handler")
|
|
|
30
30
|
var import_custom_div_handler = require("./mdast-handlers/custom-div-handler");
|
|
31
31
|
var import_custom_img_handler = require("./mdast-handlers/custom-img-handler");
|
|
32
32
|
var import_custom_table_handler = require("./mdast-handlers/custom-table-handler");
|
|
33
|
-
var import_empty_handler = require("./mdast-handlers/empty-handler");
|
|
34
33
|
var import_math_handler = require("./mdast-handlers/math-handler");
|
|
34
|
+
var import_hast_utils = require("./utils/hast-utils");
|
|
35
35
|
const htmlToMdast = (htmlOrHast, options) => {
|
|
36
|
-
const { extractors } = options || {};
|
|
37
|
-
const hast = typeof htmlOrHast === "string" ? (0, import_hast_util_from_html.fromHtml)(htmlOrHast, { fragment: true }) : htmlOrHast;
|
|
38
|
-
const
|
|
36
|
+
const { extractors, url: defaultUrl, lang: defaultLang } = options || {};
|
|
37
|
+
const [lang, hast] = typeof htmlOrHast === "string" ? [defaultLang || (0, import_hast_utils.getLangFromStr)(htmlOrHast), (0, import_hast_util_from_html.fromHtml)(htmlOrHast, { fragment: true })] : [defaultLang || (0, import_hast_utils.getLangFromHast)(htmlOrHast), htmlOrHast];
|
|
38
|
+
const url = defaultUrl || (0, import_hast_utils.getUrlFromHast)(hast);
|
|
39
|
+
const extractedHast = (0, import_extract_hast.extractHast)({ hast, lang, url }, extractors);
|
|
39
40
|
const mdast = (0, import_hast_util_to_mdast.toMdast)(extractedHast, {
|
|
40
41
|
handlers: {
|
|
41
42
|
math: import_math_handler.mathHandler,
|
|
@@ -43,8 +44,7 @@ const htmlToMdast = (htmlOrHast, options) => {
|
|
|
43
44
|
pre: import_custom_code_handler.customCodeHandler,
|
|
44
45
|
a: (0, import_custom_a_handler.customAHandler)({ asText: options?.linkAsText }),
|
|
45
46
|
img: (0, import_custom_img_handler.customImgHandler)({ hideImage: options?.hideImage }),
|
|
46
|
-
table: (0, import_custom_table_handler.customTableHandler)({ asText: options?.tableAsText })
|
|
47
|
-
br: import_empty_handler.emptyHandler
|
|
47
|
+
table: (0, import_custom_table_handler.customTableHandler)({ asText: options?.tableAsText })
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
const extractedMdast = (0, import_extract_mdast.extractMdast)(mdast);
|
|
@@ -21,13 +21,18 @@ __export(link_replacer_exports, {
|
|
|
21
21
|
linkReplacer: () => linkReplacer
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(link_replacer_exports);
|
|
24
|
-
const linkReplacer = (markdown,
|
|
25
|
-
const regex = /(!?\[.*?\]\()(
|
|
26
|
-
return markdown.replace(regex, (match, pre,
|
|
27
|
-
if (
|
|
24
|
+
const linkReplacer = (markdown, base) => {
|
|
25
|
+
const regex = /(!?\[.*?\]\()([^)\s]+)(\))/g;
|
|
26
|
+
return markdown.replace(regex, (match, pre, url, post) => {
|
|
27
|
+
if (/^(https?:|#)/.test(url)) {
|
|
28
|
+
return match;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const absoluteUrl = new URL(url, base).href;
|
|
32
|
+
return `${pre}${absoluteUrl}${post}`;
|
|
33
|
+
} catch {
|
|
28
34
|
return match;
|
|
29
35
|
}
|
|
30
|
-
return `${pre}${new URL(thisUrl, link).href}${post}`;
|
|
31
36
|
});
|
|
32
37
|
};
|
|
33
38
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -21,15 +21,44 @@ __export(playwright_exports, {
|
|
|
21
21
|
loadHtml: () => loadHtml
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(playwright_exports);
|
|
24
|
-
var
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
var import_playwright_core = require("playwright-core");
|
|
25
|
+
const SUPER_BYPASS_DEVICE = {
|
|
26
|
+
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
|
27
|
+
viewport: { width: 1920, height: 1080 },
|
|
28
|
+
deviceScaleFactor: 1,
|
|
29
|
+
hasTouch: false,
|
|
30
|
+
isMobile: false,
|
|
31
|
+
javaScriptEnabled: true,
|
|
32
|
+
locale: "en-US",
|
|
33
|
+
timezoneId: "America/New_York"
|
|
34
|
+
};
|
|
35
|
+
const loadHtml = async (url, options) => {
|
|
36
|
+
const { browser, waitUntil, timeout, superBypassMode } = options ?? {};
|
|
37
|
+
const _browser = browser ?? await import_playwright_core.chromium.launch({ headless: true });
|
|
38
|
+
const context = await _browser.newContext(superBypassMode ? SUPER_BYPASS_DEVICE : import_playwright_core.devices["Desktop Chrome"]);
|
|
39
|
+
if (superBypassMode) {
|
|
40
|
+
await context.addInitScript(() => {
|
|
41
|
+
Object.defineProperty(navigator, "webdriver", { get: () => void 0 });
|
|
42
|
+
Object.defineProperty(navigator, "languages", { get: () => ["en-US", "en"] });
|
|
43
|
+
Object.defineProperty(navigator, "plugins", { get: () => [1, 2, 3, 4, 5] });
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const page = await context.newPage();
|
|
47
|
+
if (superBypassMode) {
|
|
48
|
+
await page.route("**/*.js", (route) => {
|
|
49
|
+
if (route.request().url().includes("captcha-delivery")) {
|
|
50
|
+
return route.abort();
|
|
51
|
+
}
|
|
52
|
+
return route.continue();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
await page.goto(url, { waitUntil: waitUntil ?? "load", timeout });
|
|
29
56
|
const html = await page.content();
|
|
30
57
|
await page.close();
|
|
31
|
-
if (
|
|
32
|
-
await
|
|
58
|
+
if (browser) {
|
|
59
|
+
await context.close();
|
|
60
|
+
} else {
|
|
61
|
+
await _browser.close();
|
|
33
62
|
}
|
|
34
63
|
return html;
|
|
35
64
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var custom_br_handler_exports = {};
|
|
20
|
+
__export(custom_br_handler_exports, {
|
|
21
|
+
customBrHandler: () => customBrHandler
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(custom_br_handler_exports);
|
|
24
|
+
var import_hast_util_to_mdast = require("hast-util-to-mdast");
|
|
25
|
+
const customBrHandler = (state, node) => {
|
|
26
|
+
return import_hast_util_to_mdast.defaultHandlers.br(state, node);
|
|
27
|
+
};
|
|
28
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
29
|
+
0 && (module.exports = {
|
|
30
|
+
customBrHandler
|
|
31
|
+
});
|
|
@@ -33,7 +33,7 @@ const DEFAULT_MDAST_TO_MARKDOWN_OPTIONS = {
|
|
|
33
33
|
};
|
|
34
34
|
const mdastToMarkdown = (mdast, options) => {
|
|
35
35
|
const { baseUrl, ...toMarkdownOptions } = { ...DEFAULT_MDAST_TO_MARKDOWN_OPTIONS, ...options };
|
|
36
|
-
let markdown = (0, import_mdast_util_to_markdown.toMarkdown)((0, import_mdast_utils.warpRoot)(mdast), toMarkdownOptions);
|
|
36
|
+
let markdown = (0, import_mdast_util_to_markdown.toMarkdown)((0, import_mdast_utils.warpRoot)(mdast), toMarkdownOptions).replace(/\*\*\*\*/g, "");
|
|
37
37
|
if (baseUrl) {
|
|
38
38
|
markdown = (0, import_link_replacer.linkReplacer)(markdown, baseUrl);
|
|
39
39
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var hast_utils_exports = {};
|
|
20
|
+
__export(hast_utils_exports, {
|
|
21
|
+
getLangFromHast: () => getLangFromHast,
|
|
22
|
+
getLangFromStr: () => getLangFromStr,
|
|
23
|
+
getUrlFromHast: () => getUrlFromHast
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(hast_utils_exports);
|
|
26
|
+
var import_hast_util_select = require("hast-util-select");
|
|
27
|
+
const getLangFromHast = (node) => {
|
|
28
|
+
const html = (0, import_hast_util_select.select)("html", node);
|
|
29
|
+
if (html && typeof html.properties.lang === "string") {
|
|
30
|
+
return html.properties.lang;
|
|
31
|
+
}
|
|
32
|
+
if (node.type !== "element") {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const element = node;
|
|
36
|
+
if (element.tagName !== "html") {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const langAttr = element.properties.lang || element.properties["xml:lang"];
|
|
40
|
+
if (langAttr) {
|
|
41
|
+
return langAttr;
|
|
42
|
+
}
|
|
43
|
+
return void 0;
|
|
44
|
+
};
|
|
45
|
+
const getLangFromStr = (str) => {
|
|
46
|
+
const match = str.match(/lang=["']([^"']+)["']/);
|
|
47
|
+
if (match) {
|
|
48
|
+
return match[1];
|
|
49
|
+
}
|
|
50
|
+
return void 0;
|
|
51
|
+
};
|
|
52
|
+
const getUrlFromHast = (node) => {
|
|
53
|
+
if (node.type !== "element") {
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
56
|
+
const metaTagAttributes = ["og:url", "twitter:url"];
|
|
57
|
+
const metaTags = (0, import_hast_util_select.selectAll)("meta", node);
|
|
58
|
+
for (const meta of metaTags) {
|
|
59
|
+
const property = meta.properties.property || meta.properties.name;
|
|
60
|
+
if (typeof property === "string" && metaTagAttributes.includes(property)) {
|
|
61
|
+
return typeof meta.properties.content === "string" ? meta.properties.content : void 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return void 0;
|
|
65
|
+
};
|
|
66
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
67
|
+
0 && (module.exports = {
|
|
68
|
+
getLangFromHast,
|
|
69
|
+
getLangFromStr,
|
|
70
|
+
getUrlFromHast
|
|
71
|
+
});
|
package/dist/cli/bin.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/cli/bin.ts
|
|
2
|
+
import { intro } from "@clack/prompts";
|
|
3
|
+
import { program } from "commander";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import packageInfo from "../../package.json";
|
|
6
|
+
import { webforaiCommand } from "./commands/webforai/index.js";
|
|
7
|
+
import { LOADERS, MODES } from "./constants.js";
|
|
8
|
+
program.name("webforai").description("CLI tool for ultra-precise HTML to Markdown conversion").version(packageInfo.version, "-v, --version", "output the current version");
|
|
9
|
+
program.argument("[source]", "URL or path to process").option("-o, --output <output>", "Path to output file or directory").option("-m, --mode <mode>", `Processing mode (${MODES.join(", ")})`).option("-l, --loader <loader>", `Loader to use (${LOADERS.join(", ")})`).option("-d, --debug", "output extra debugging information").action(webforaiCommand);
|
|
10
|
+
intro(pc.inverse(`webforai CLI version ${packageInfo.version}`));
|
|
11
|
+
program.parse();
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/cli/commands/webforai/index.ts
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { intro, outro, spinner } from "@clack/prompts";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
import packageInfo from "../../../../package.json";
|
|
7
|
+
import { htmlToMarkdown } from "../../../html-to-markdown.js";
|
|
8
|
+
import { inputOutputPath } from "../../helpers/inputOutputPath.js";
|
|
9
|
+
import { inputSourcePath } from "../../helpers/inputSourcePath.js";
|
|
10
|
+
import { selectExtractMode } from "../../helpers/selectExtractMode.js";
|
|
11
|
+
import { selectLoader } from "../../helpers/selectLoader.js";
|
|
12
|
+
import { isUrl } from "../../utils.js";
|
|
13
|
+
import { loadHtml } from "./loadHtml.js";
|
|
14
|
+
var aiModeOptions = { linkAsText: true, tableAsText: true, hideImage: true };
|
|
15
|
+
var readabilityModeOptions = { linkAsText: false, tableAsText: false, hideImage: false };
|
|
16
|
+
var webforaiCommand = async (initialPath, options) => {
|
|
17
|
+
intro(pc.bold(pc.green(`webforai CLI version ${packageInfo.version}`)));
|
|
18
|
+
const sourcePath = initialPath ?? await inputSourcePath();
|
|
19
|
+
options.debug && console.debug(`sourcePath: ${sourcePath}`);
|
|
20
|
+
const loader = isUrl(sourcePath) ? options.loader ?? await selectLoader() : "local";
|
|
21
|
+
options.debug && console.debug(`loader: ${loader}`);
|
|
22
|
+
const outputPath = options.output ?? await inputOutputPath(sourcePath);
|
|
23
|
+
options.debug && console.debug(`outputPath: ${outputPath}`);
|
|
24
|
+
const mode = options.mode ?? await selectExtractMode();
|
|
25
|
+
options.debug && console.debug(`mode: ${mode}`);
|
|
26
|
+
let html;
|
|
27
|
+
const s = spinner();
|
|
28
|
+
try {
|
|
29
|
+
s.start("Loading content...");
|
|
30
|
+
html = await loadHtml(sourcePath, loader, { debug: options.debug });
|
|
31
|
+
s.stop(pc.green("Content loaded!"));
|
|
32
|
+
} catch (error) {
|
|
33
|
+
s.stop(pc.red("Content loading failed!"));
|
|
34
|
+
console.error(error);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
options.debug && console.debug(`html: ${html}`);
|
|
38
|
+
const markdown = htmlToMarkdown(html, {
|
|
39
|
+
baseUrl: isUrl(sourcePath) ? sourcePath : void 0,
|
|
40
|
+
...mode === "ai" ? aiModeOptions : readabilityModeOptions
|
|
41
|
+
});
|
|
42
|
+
options.debug && console.debug(`markdown: ${markdown}`);
|
|
43
|
+
const directory = path.dirname(outputPath);
|
|
44
|
+
const isDirectoryExists = await fs.stat(directory).then((stat) => stat.isDirectory());
|
|
45
|
+
if (!isDirectoryExists) {
|
|
46
|
+
await fs.mkdir(directory, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
await fs.writeFile(outputPath, markdown);
|
|
49
|
+
outro(pc.green(`${pc.bold("Done!")} Markdown saved to ${outputPath}`));
|
|
50
|
+
};
|
|
51
|
+
export {
|
|
52
|
+
webforaiCommand
|
|
53
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// src/cli/commands/webforai/loadHtml.ts
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import pc from "picocolors";
|
|
4
|
+
import { chromium } from "playwright-core";
|
|
5
|
+
import playwrightPackageJson from "playwright-core/package.json";
|
|
6
|
+
import { loadHtml as loadHtmlByFetch } from "../../../loaders/fetch.js";
|
|
7
|
+
import { loadHtml as loadHtmlByPlaywright } from "../../../loaders/playwright.js";
|
|
8
|
+
var checkPlaywrightAvailable = async () => {
|
|
9
|
+
const path = chromium.executablePath();
|
|
10
|
+
const isAvailable = await fs.access(path).catch(() => false);
|
|
11
|
+
return !!isAvailable;
|
|
12
|
+
};
|
|
13
|
+
var loadHtml = async (sourcePath, loader, options) => {
|
|
14
|
+
if (loader === "local") {
|
|
15
|
+
options.debug && console.debug(`Loading HTML from local file: ${sourcePath}`);
|
|
16
|
+
const content = await fs.readFile(sourcePath, "utf-8");
|
|
17
|
+
options.debug && console.debug(`HTML loaded: ${content.slice(0, 100)}`);
|
|
18
|
+
return content;
|
|
19
|
+
}
|
|
20
|
+
if (loader === "fetch") {
|
|
21
|
+
options.debug && console.debug(`Loading HTML from URL: ${sourcePath}`);
|
|
22
|
+
const content = await loadHtmlByFetch(sourcePath);
|
|
23
|
+
options.debug && console.debug(`HTML loaded: ${content.slice(0, 100)}`);
|
|
24
|
+
return content;
|
|
25
|
+
}
|
|
26
|
+
if (loader === "playwright") {
|
|
27
|
+
options.debug && console.debug(`Loading HTML from playwright: ${sourcePath}`);
|
|
28
|
+
const isPlaywrightAvailable = await checkPlaywrightAvailable();
|
|
29
|
+
options.debug && console.debug(`Playwright available: ${isPlaywrightAvailable}`);
|
|
30
|
+
if (!isPlaywrightAvailable) {
|
|
31
|
+
const message = [
|
|
32
|
+
"To use playwright loader, you need to install playwright.",
|
|
33
|
+
"You can install it by running:",
|
|
34
|
+
"",
|
|
35
|
+
` ${pc.bold(`npx playwright@${playwrightPackageJson.version} install chromium`)} `,
|
|
36
|
+
"",
|
|
37
|
+
"Hint1: If you get warning like this:",
|
|
38
|
+
"WARNING: It looks like you are running 'npx playwright install' without first installing your project's dependencies. ",
|
|
39
|
+
`${pc.bold("Ignore this warning.")}`,
|
|
40
|
+
"",
|
|
41
|
+
"Hint2: If you get message like this:",
|
|
42
|
+
"Host system is missing dependencies to run browsers.",
|
|
43
|
+
"You should install dependencies by running:",
|
|
44
|
+
"",
|
|
45
|
+
` ${pc.bold(`sudo npx playwright@${playwrightPackageJson.version} install-deps`)} `,
|
|
46
|
+
"",
|
|
47
|
+
pc.gray("Note: Good luck with that.")
|
|
48
|
+
];
|
|
49
|
+
console.info(message.join("\n"));
|
|
50
|
+
throw new Error("Playwright is not available");
|
|
51
|
+
}
|
|
52
|
+
const content = await loadHtmlByPlaywright(sourcePath);
|
|
53
|
+
options.debug && console.debug(`HTML loaded: ${content.slice(0, 100)}`);
|
|
54
|
+
return content;
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`Unsupported loader: ${loader}`);
|
|
57
|
+
};
|
|
58
|
+
export {
|
|
59
|
+
loadHtml
|
|
60
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/cli/helpers/assertContinue.ts
|
|
2
|
+
import { cancel, isCancel } from "@clack/prompts";
|
|
3
|
+
function assertContinue(message, cancelMessage = "Canceled.") {
|
|
4
|
+
if (isCancel(message)) {
|
|
5
|
+
cancel(cancelMessage);
|
|
6
|
+
process.exit(1);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
assertContinue
|
|
11
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/cli/helpers/inputOutputPath.ts
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { confirm, text } from "@clack/prompts";
|
|
4
|
+
import { getNextAvailableFilePath, sourcePathToOutputPath } from "../utils.js";
|
|
5
|
+
import { assertContinue } from "./assertContinue.js";
|
|
6
|
+
var inputOutputPath = async (sourcePath) => {
|
|
7
|
+
const outputPath = await text({
|
|
8
|
+
message: "Enter the output file path:",
|
|
9
|
+
placeholder: "output.md",
|
|
10
|
+
initialValue: sourcePathToOutputPath(sourcePath),
|
|
11
|
+
validate: (value) => {
|
|
12
|
+
if (value.trim() === "") {
|
|
13
|
+
return "Output path is required";
|
|
14
|
+
}
|
|
15
|
+
if (fs.existsSync(value) && fs.statSync(value).isDirectory()) {
|
|
16
|
+
return "No directory can be specified.";
|
|
17
|
+
}
|
|
18
|
+
if (!fs.existsSync(value) && value.endsWith("/")) {
|
|
19
|
+
return "No directory can be specified.";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
assertContinue(outputPath);
|
|
24
|
+
if (!fs.existsSync(outputPath)) {
|
|
25
|
+
return outputPath;
|
|
26
|
+
}
|
|
27
|
+
const isOutputFileOverwrite = await confirm({
|
|
28
|
+
message: "The file already exists. Overwrite?",
|
|
29
|
+
initialValue: false
|
|
30
|
+
});
|
|
31
|
+
assertContinue(isOutputFileOverwrite);
|
|
32
|
+
if (isOutputFileOverwrite) {
|
|
33
|
+
return outputPath;
|
|
34
|
+
}
|
|
35
|
+
const escapedOutputPath = await text({
|
|
36
|
+
message: "Enter the output file path:",
|
|
37
|
+
placeholder: "output.md",
|
|
38
|
+
initialValue: getNextAvailableFilePath(outputPath),
|
|
39
|
+
validate: (value) => {
|
|
40
|
+
if (value.trim() === "") {
|
|
41
|
+
return "Output path is required";
|
|
42
|
+
}
|
|
43
|
+
if (fs.existsSync(value)) {
|
|
44
|
+
return "The file already exists";
|
|
45
|
+
}
|
|
46
|
+
if (!fs.existsSync(value) && value.endsWith("/")) {
|
|
47
|
+
return "No directory can be specified.";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
assertContinue(escapedOutputPath);
|
|
52
|
+
return escapedOutputPath;
|
|
53
|
+
};
|
|
54
|
+
export {
|
|
55
|
+
inputOutputPath
|
|
56
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// src/cli/helpers/inputSourcePath.ts
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { text } from "@clack/prompts";
|
|
4
|
+
import { DEFAULT_PATH } from "../constants.js";
|
|
5
|
+
import { isUrl } from "../utils.js";
|
|
6
|
+
import { assertContinue } from "./assertContinue.js";
|
|
7
|
+
var inputSourcePath = async () => {
|
|
8
|
+
const result = await text({
|
|
9
|
+
message: "Enter the URL or html path to be converted to markdown:",
|
|
10
|
+
placeholder: DEFAULT_PATH,
|
|
11
|
+
initialValue: "",
|
|
12
|
+
validate: (value) => {
|
|
13
|
+
if (value.trim() === "") {
|
|
14
|
+
return "Source is required";
|
|
15
|
+
}
|
|
16
|
+
if (!isUrl(value)) {
|
|
17
|
+
if (!fs.existsSync(value)) {
|
|
18
|
+
return "It appears that you are specifying a local file, but the file cannot be found. hint: when specifying a url, start with http or https.";
|
|
19
|
+
}
|
|
20
|
+
if (fs.statSync(value).isDirectory()) {
|
|
21
|
+
return "You are specifying a local file, but you cannot specify a directory. hint: when specifying a url, start with http or https.";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
assertContinue(result);
|
|
27
|
+
return result;
|
|
28
|
+
};
|
|
29
|
+
export {
|
|
30
|
+
inputSourcePath
|
|
31
|
+
};
|