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
package/dist/bin.js
CHANGED
|
@@ -1,233 +1,992 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/bin.ts
|
|
4
|
-
import
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { cancel, confirm, intro, isCancel, outro, select, spinner, text } from "@clack/prompts";
|
|
3
|
+
// src/cli/bin.ts
|
|
4
|
+
import { intro as intro2 } from "@clack/prompts";
|
|
7
5
|
import { program } from "commander";
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
import pc3 from "picocolors";
|
|
7
|
+
|
|
8
|
+
// package.json
|
|
9
|
+
var package_default = {
|
|
10
|
+
name: "webforai",
|
|
11
|
+
version: "1.6.0",
|
|
12
|
+
description: "A library that provides a web interface for AI",
|
|
13
|
+
author: "inaridiy",
|
|
14
|
+
license: "MIT",
|
|
15
|
+
keywords: [
|
|
16
|
+
"web",
|
|
17
|
+
"ai",
|
|
18
|
+
"html",
|
|
19
|
+
"html2md",
|
|
20
|
+
"markdown",
|
|
21
|
+
"mdast",
|
|
22
|
+
"hast"
|
|
23
|
+
],
|
|
24
|
+
repository: {
|
|
25
|
+
type: "git",
|
|
26
|
+
url: "https://github.com/inaridiy/webforai.git"
|
|
27
|
+
},
|
|
28
|
+
homepage: "https://github.com/inaridiy/webforai.git",
|
|
29
|
+
scripts: {
|
|
30
|
+
"copy:package.cjs.json": "pnpm ncp ./package.cjs.json ./dist/cjs/package.json && pnpm ncp ./package.cjs.json ./dist/types/package.json ",
|
|
31
|
+
clean: "rimraf dist",
|
|
32
|
+
build: "pnpm clean && tsx build.ts && pnpm copy:package.cjs.json",
|
|
33
|
+
test: 'echo "Error: no test specified"',
|
|
34
|
+
prerelease: "pnpm build",
|
|
35
|
+
release: "np"
|
|
36
|
+
},
|
|
37
|
+
files: [
|
|
38
|
+
"dist",
|
|
39
|
+
"!dist/types/**/*.js"
|
|
40
|
+
],
|
|
41
|
+
main: "dist/cjs/index.js",
|
|
42
|
+
type: "module",
|
|
43
|
+
module: "dist/index.js",
|
|
44
|
+
types: "dist/types/index.d.ts",
|
|
45
|
+
bin: "dist/bin.js",
|
|
46
|
+
exports: {
|
|
47
|
+
".": {
|
|
48
|
+
types: "./dist/types/index.d.ts",
|
|
49
|
+
import: "./dist/index.js",
|
|
50
|
+
require: "./dist/cjs/index.js"
|
|
51
|
+
},
|
|
52
|
+
"./types": {
|
|
53
|
+
types: "./dist/types/index.d.ts",
|
|
54
|
+
import: "./dist/index.js",
|
|
55
|
+
require: "./dist/cjs/index.js"
|
|
56
|
+
},
|
|
57
|
+
"./loaders/playwright": {
|
|
58
|
+
types: "./dist/types/loaders/playwright.d.ts",
|
|
59
|
+
import: "./dist/loaders/playwright.js",
|
|
60
|
+
require: "./dist/cjs/loaders/playwright.js"
|
|
61
|
+
},
|
|
62
|
+
"./loaders/fetch": {
|
|
63
|
+
types: "./dist/types/loaders/fetch.d.ts",
|
|
64
|
+
import: "./dist/loaders/fetch.js",
|
|
65
|
+
require: "./dist/cjs/loaders/fetch.js"
|
|
66
|
+
},
|
|
67
|
+
"./loaders/cf-puppeteer": {
|
|
68
|
+
types: "./dist/types/loaders/cf-puppeteer.d.ts",
|
|
69
|
+
import: "./dist/loaders/cf-puppeteer.js",
|
|
70
|
+
require: "./dist/cjs/loaders/cf-puppeteer.js"
|
|
71
|
+
},
|
|
72
|
+
"./loaders/puppeteer": {
|
|
73
|
+
types: "./dist/types/loaders/puppeteer.d.ts",
|
|
74
|
+
import: "./dist/loaders/puppeteer.js",
|
|
75
|
+
require: "./dist/cjs/loaders/puppeteer.js"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
typesVersions: {
|
|
79
|
+
"*": {
|
|
80
|
+
types: [
|
|
81
|
+
"./dist/types/index.d.ts"
|
|
82
|
+
],
|
|
83
|
+
"loaders/playwright": [
|
|
84
|
+
"./dist/types/loaders/playwright.d.ts"
|
|
85
|
+
],
|
|
86
|
+
"loaders/cf-puppeteer": [
|
|
87
|
+
"./dist/types/loaders/cf-puppeteer.d.ts"
|
|
88
|
+
],
|
|
89
|
+
"loaders/fetch": [
|
|
90
|
+
"./dist/types/loaders/fetch.d.ts"
|
|
91
|
+
],
|
|
92
|
+
"loaders/puppeteer": [
|
|
93
|
+
"./dist/types/loaders/puppeteer.d.ts"
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
peerDependencies: {
|
|
98
|
+
"@cloudflare/puppeteer": ">=0.0.6",
|
|
99
|
+
"playwright-core": ">=1.4",
|
|
100
|
+
puppeteer: ">=22"
|
|
101
|
+
},
|
|
102
|
+
peerDependenciesMeta: {
|
|
103
|
+
"@cloudflare/puppeteer": {
|
|
104
|
+
optional: true
|
|
105
|
+
},
|
|
106
|
+
"playwright-core": {
|
|
107
|
+
optional: false
|
|
108
|
+
},
|
|
109
|
+
puppeteer: {
|
|
110
|
+
optional: true
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
dependencies: {
|
|
114
|
+
"@clack/prompts": "^0.7.0",
|
|
115
|
+
commander: "^12.1.0",
|
|
116
|
+
"hast-util-from-html": "^2.0.3",
|
|
117
|
+
"hast-util-select": "^6.0.2",
|
|
118
|
+
"hast-util-to-html": "^9.0.3",
|
|
119
|
+
"hast-util-to-mdast": "^10.1.0",
|
|
120
|
+
"hast-util-to-string": "^3.0.0",
|
|
121
|
+
"hast-util-to-text": "^4.0.0",
|
|
122
|
+
"mathml-to-latex": "^1.4.1",
|
|
123
|
+
"mdast-util-gfm": "^3.0.0",
|
|
124
|
+
"mdast-util-math": "^3.0.0",
|
|
125
|
+
"mdast-util-to-markdown": "^2.1.0",
|
|
126
|
+
picocolors: "^1.0.1",
|
|
127
|
+
"trim-trailing-lines": "^2.1.0",
|
|
128
|
+
"unist-util-filter": "^5.0.1",
|
|
129
|
+
zx: "^8.1.5"
|
|
130
|
+
},
|
|
131
|
+
devDependencies: {
|
|
132
|
+
"@cloudflare/puppeteer": "^0.0.6",
|
|
133
|
+
"@tsconfig/recommended": "^1.0.3",
|
|
134
|
+
"fastest-levenshtein": "^1.0.16",
|
|
135
|
+
"@types/hast": "^3.0.2",
|
|
136
|
+
"@types/mdast": "^4.0.2",
|
|
137
|
+
"@types/node": "^20.14.10",
|
|
138
|
+
arg: "^5.0.2",
|
|
139
|
+
esbuild: "^0.19.11",
|
|
140
|
+
glob: "^10.3.10",
|
|
141
|
+
ncp: "^2.0.0",
|
|
142
|
+
np: "^9.2.0",
|
|
143
|
+
"playwright-core": "^1.40.1",
|
|
144
|
+
puppeteer: "^23.2.2",
|
|
145
|
+
rimraf: "^5.0.5",
|
|
146
|
+
tsx: "^4.19.1",
|
|
147
|
+
typescript: "^5.4.5"
|
|
148
|
+
}
|
|
20
149
|
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
150
|
+
|
|
151
|
+
// src/cli/commands/webforai/index.ts
|
|
152
|
+
import fs5 from "node:fs/promises";
|
|
153
|
+
import path2 from "node:path";
|
|
154
|
+
import { intro, outro, spinner } from "@clack/prompts";
|
|
155
|
+
import pc2 from "picocolors";
|
|
156
|
+
|
|
157
|
+
// src/html-to-mdast.ts
|
|
158
|
+
import { fromHtml } from "hast-util-from-html";
|
|
159
|
+
import { toMdast } from "hast-util-to-mdast";
|
|
160
|
+
|
|
161
|
+
// src/extract-hast/readability.ts
|
|
162
|
+
import { select, selectAll } from "hast-util-select";
|
|
163
|
+
import { toString as hastToString } from "hast-util-to-string";
|
|
164
|
+
import { filter } from "unist-util-filter";
|
|
165
|
+
|
|
166
|
+
// src/extract-hast/utils.ts
|
|
167
|
+
var matchString = (element) => `${element.tagName} ${element.properties.id} ${classnames(element).join(" ")}`;
|
|
168
|
+
var classnames = (element) => {
|
|
169
|
+
if (Array.isArray(element.properties.className)) {
|
|
170
|
+
return element.properties.className;
|
|
171
|
+
}
|
|
172
|
+
return [];
|
|
173
|
+
};
|
|
174
|
+
var isStrInclude = (value, match) => {
|
|
175
|
+
if (typeof value === "string") {
|
|
176
|
+
return value.includes(match);
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// src/extract-hast/readability.ts
|
|
182
|
+
var UNLIKELY_ROLES = ["menu", "menubar", "complementary", "navigation", "alert", "alertdialog", "dialog"];
|
|
183
|
+
var REGEXPS = {
|
|
184
|
+
hidden: /hidden|invisible|fallback-image/i,
|
|
185
|
+
byline: /byline|author|dateline|writtenby|p-author/i,
|
|
186
|
+
specialUnlikelyCandidates: /frb-|uls-menu|language-link/i,
|
|
187
|
+
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,
|
|
188
|
+
okMaybeItsaCandidate: /and|article|body|column|content|main|shadow|code/i
|
|
189
|
+
};
|
|
190
|
+
var BODY_SELECTORS = ["article", "#article", ".article_body", ".article-body", "#content", ".entry"];
|
|
191
|
+
var PARAGRAPH_TAGS = ["a", "p", "div", "section", "article", "main", "ul", "ol", "li"];
|
|
192
|
+
var BASE_MINIMAL_LENGTH = {
|
|
193
|
+
ja: 200,
|
|
194
|
+
en: 500
|
|
195
|
+
};
|
|
196
|
+
var metadataFilter = (node) => {
|
|
197
|
+
return !(["comment", "doctype"].includes(node.type) || node.type === "element" && ["script", "style", "link", "meta", "noscript", "svg", "title"].includes(node.tagName));
|
|
198
|
+
};
|
|
199
|
+
var universalElementFilter = (node) => {
|
|
200
|
+
if (node.type !== "element") {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
const element = node;
|
|
204
|
+
if (["aside", "nav"].includes(element.tagName)) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
if (["hidden", "aria-hidden"].some((key) => element.properties[key])) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
if (classnames(element).some((classname) => REGEXPS.hidden.test(classname))) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
if (element.tagName === "dialog") {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
if (element.properties.role === "dialog" && element.properties["aria-modal"]) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
if (element.properties.rel === "author" && isStrInclude(element.properties.itemprop, "author")) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
if (REGEXPS.byline.test(matchString(element))) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
if (element.properties.role && UNLIKELY_ROLES.includes(element.properties.role)) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
};
|
|
230
|
+
var unlikelyElementFilter = (node) => {
|
|
231
|
+
if (node.type !== "element") {
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
const element = node;
|
|
235
|
+
if (["body", "article", "main", "section", "h1", "h2", "h3", "h4", "h5", "h6", "p"].includes(element.tagName)) {
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
const match = matchString(element);
|
|
239
|
+
if (REGEXPS.specialUnlikelyCandidates.test(match)) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
if (REGEXPS.unlikelyCandidates.test(match) && !REGEXPS.okMaybeItsaCandidate.test(match)) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
246
|
+
};
|
|
247
|
+
var removeEmptyFilter = (node, _lang) => {
|
|
248
|
+
if (node.type !== "element") {
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
const element = node;
|
|
252
|
+
if (PARAGRAPH_TAGS.includes(element.tagName)) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
if (element.tagName === "img" && !element.properties.src) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
return true;
|
|
259
|
+
};
|
|
260
|
+
var readabilityExtractHast = (params) => {
|
|
261
|
+
const { hast, lang = "en" } = params;
|
|
262
|
+
const body = select("body", hast) ?? hast;
|
|
263
|
+
const metadataFilteredHast = filter(body, (node) => metadataFilter(node));
|
|
264
|
+
const metadataFilteredHastText = metadataFilteredHast && hastToString(metadataFilteredHast);
|
|
265
|
+
if (!(metadataFilteredHast && metadataFilteredHastText)) {
|
|
266
|
+
return body;
|
|
267
|
+
}
|
|
268
|
+
const baseFilterd = filter(metadataFilteredHast, (node) => universalElementFilter(node));
|
|
269
|
+
const baseFilterdText = baseFilterd ? hastToString(baseFilterd) : "";
|
|
270
|
+
const [baseTree, baseText] = baseFilterdText.length > metadataFilteredHastText.length / 3 || baseFilterdText.length > 5e3 ? [baseFilterd, baseFilterdText] : [metadataFilteredHast, metadataFilteredHastText];
|
|
271
|
+
let minimalLength = lang in BASE_MINIMAL_LENGTH ? BASE_MINIMAL_LENGTH[lang] : 500;
|
|
272
|
+
if (baseText.length < minimalLength) {
|
|
273
|
+
minimalLength = Math.max(0, baseText.length - 200);
|
|
274
|
+
}
|
|
275
|
+
let extractedTree = baseTree;
|
|
276
|
+
let extractedText = baseText;
|
|
277
|
+
for (const selector of BODY_SELECTORS) {
|
|
278
|
+
const content = { type: "root", children: selectAll(selector, baseFilterd) };
|
|
279
|
+
const contentText = hastToString(content);
|
|
280
|
+
if (contentText.length < 25) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const links = selectAll("a", content);
|
|
284
|
+
const linkText = links.map((link) => hastToString(link)).join("");
|
|
285
|
+
const linkDensity = linkText.length / contentText.length;
|
|
286
|
+
if (linkDensity > 0.4) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (contentText.length > minimalLength) {
|
|
290
|
+
extractedTree = content;
|
|
291
|
+
extractedText = contentText;
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const finalFilteredTree = filter(extractedTree, (node) => {
|
|
296
|
+
if (!removeEmptyFilter(node, lang)) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
if (!unlikelyElementFilter(node)) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
return true;
|
|
303
|
+
});
|
|
304
|
+
const finalTree = hastToString(finalFilteredTree).length > extractedText.length / 3 ? finalFilteredTree : extractedTree;
|
|
305
|
+
return finalTree;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// src/extract-hast/strongly.ts
|
|
309
|
+
import { select as select2 } from "hast-util-select";
|
|
310
|
+
import { filter as filter2 } from "unist-util-filter";
|
|
311
|
+
var 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;
|
|
312
|
+
var stronglyExtractHast = (params) => {
|
|
313
|
+
const { hast } = params;
|
|
314
|
+
const selectors = ["article", "#article", "#content", "main", "div.container"];
|
|
315
|
+
let selectedElement = hast;
|
|
316
|
+
for (const selector of selectors) {
|
|
317
|
+
const element = select2(selector, hast);
|
|
318
|
+
if (element) {
|
|
319
|
+
selectedElement = element;
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const removedElements = filter2(selectedElement, (node) => {
|
|
324
|
+
if (node.type === "comment") {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
if (node.type !== "element") {
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
const element = node;
|
|
331
|
+
const elementName = `${element.tagName} ${element.properties.id} ${element.properties.className}`;
|
|
332
|
+
if (REMOVE_REGEX.test(elementName)) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
return true;
|
|
336
|
+
});
|
|
337
|
+
return removedElements;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// src/extract-hast/index.ts
|
|
341
|
+
var PRESET_EXTRACT_HAST = {
|
|
342
|
+
strongly: stronglyExtractHast,
|
|
343
|
+
readability: readabilityExtractHast
|
|
344
|
+
};
|
|
345
|
+
var DEFAULT_EXTRACT_HAST = ["readability"];
|
|
346
|
+
var extractHast = (params, extractors = DEFAULT_EXTRACT_HAST) => {
|
|
347
|
+
const { hast, lang } = params;
|
|
348
|
+
const _extractors = Array.isArray(extractors) ? extractors : [extractors];
|
|
349
|
+
const extracted = _extractors.reduce((acc, extractor) => {
|
|
350
|
+
if (extractor === false) {
|
|
351
|
+
return acc;
|
|
352
|
+
}
|
|
353
|
+
if (typeof extractor === "string" && extractor in PRESET_EXTRACT_HAST) {
|
|
354
|
+
return PRESET_EXTRACT_HAST[extractor]({ hast: acc, lang });
|
|
355
|
+
}
|
|
356
|
+
if (typeof extractor === "function") {
|
|
357
|
+
return extractor({ hast: acc, lang });
|
|
358
|
+
}
|
|
359
|
+
throw new Error(`Invalid extractor: ${extractor}`);
|
|
360
|
+
}, hast) || hast;
|
|
361
|
+
return extracted;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
// src/extract-mdast.ts
|
|
365
|
+
import { filter as filter3 } from "unist-util-filter";
|
|
366
|
+
var DECLATION_TYPES = ["blockquote", "strong", "emphasis", "delete"];
|
|
367
|
+
var emptyDeclarationFilter = (node) => {
|
|
368
|
+
if (!DECLATION_TYPES.includes(node.type)) {
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
if (node.children.length === 0) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
return true;
|
|
375
|
+
};
|
|
376
|
+
var extractMdast = (node) => {
|
|
377
|
+
const extracted = filter3(node, (node2) => {
|
|
378
|
+
if (!emptyDeclarationFilter(node2)) {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
return true;
|
|
382
|
+
});
|
|
383
|
+
return extracted;
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
// src/mdast-handlers/custom-a-handler.ts
|
|
387
|
+
import { defaultHandlers } from "hast-util-to-mdast";
|
|
388
|
+
import { toString as hastToString2 } from "hast-util-to-string";
|
|
389
|
+
var customAHandler = (options) => (state, node) => {
|
|
390
|
+
if (options?.asText) {
|
|
391
|
+
const text3 = hastToString2(node);
|
|
392
|
+
if (3 >= text3.length) {
|
|
393
|
+
return void 0;
|
|
394
|
+
}
|
|
395
|
+
const link2 = { type: "text", value: text3 };
|
|
396
|
+
state.patch(node, link2);
|
|
397
|
+
return link2;
|
|
398
|
+
}
|
|
399
|
+
const link = defaultHandlers.a(state, node);
|
|
400
|
+
if (link.children.length > 0) {
|
|
401
|
+
return link;
|
|
402
|
+
}
|
|
403
|
+
return void 0;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// src/mdast-handlers/custom-code-handler.ts
|
|
407
|
+
import { toText } from "hast-util-to-text";
|
|
408
|
+
import { trimTrailingLines } from "trim-trailing-lines";
|
|
409
|
+
|
|
410
|
+
// src/utils/detect-code-lang.ts
|
|
411
|
+
var languages = [
|
|
412
|
+
["bash", [/#!(\/usr)?\/bin\/bash/g, 500], [/\b(if|elif|then|fi|echo)\b|\$/g, 10]],
|
|
413
|
+
["html", [/<\/?[a-z-]+[^\n>]*>/g, 10], [/^\s+<!DOCTYPE\s+html/g, 500]],
|
|
414
|
+
["http", [/^(GET|HEAD|POST|PUT|DELETE|PATCH|HTTP)\b/g, 500]],
|
|
415
|
+
[
|
|
416
|
+
"ts",
|
|
417
|
+
[/(import .* from|export \*|export const|const .* = await)/g, 400],
|
|
418
|
+
[
|
|
419
|
+
/\b(console|await|async|function|undefined|export|import|this|class|for|let|const|map|join|require|implements|interface|namespace)\b/g,
|
|
420
|
+
10
|
|
421
|
+
]
|
|
422
|
+
],
|
|
423
|
+
[
|
|
424
|
+
"tsx",
|
|
425
|
+
[/(import .* from|export \*|export const)/g, 400],
|
|
426
|
+
[/\b(react|next|FC)\b/g, 200],
|
|
427
|
+
[
|
|
428
|
+
/\b(console|await|async|function|export|undefined|import|this|class|for|let|const|map|join|require|implements|interface)\b/g,
|
|
429
|
+
10
|
|
430
|
+
]
|
|
431
|
+
],
|
|
432
|
+
["py", [/\b(def|print|class|and|or|lambda)\b/g, 10]],
|
|
433
|
+
["sql", [/\b(SELECT|INSERT|FROM)\b/g, 50]],
|
|
434
|
+
["pl", [/#!(\/usr)?\/bin\/perl/g, 500], [/\b(use|print)\b|\$/g, 10]],
|
|
435
|
+
["lua", [/#!(\/usr)?\/bin\/lua/g, 500]],
|
|
436
|
+
["make", [/\b(ifneq|endif|if|elif|then|fi|echo|.PHONY|^[a-z]+ ?:$)\b|\$/gm, 10]],
|
|
437
|
+
["uri", [/https?:|mailto:|tel:|ftp:/g, 30]],
|
|
438
|
+
["css", [/^(@import|@page|@media|(\.|#)[a-z]+)/gm, 20]],
|
|
439
|
+
["diff", [/^[+><-]/gm, 10], [/^@@ ?[-+,0-9 ]+ ?@@/gm, 25]],
|
|
440
|
+
["md", [/^(>|\t\*|\t\d+.)/gm, 10], [/\[.*\](.*)/g, 10]],
|
|
441
|
+
["docker", [/^(FROM|ENTRYPOINT|RUN)/gm, 500]],
|
|
442
|
+
["xml", [/<\/?[a-z-]+[^\n>]*>/g, 10], [/^<\?xml/g, 500]],
|
|
443
|
+
["c", [/#include\b|\bprintf\s+\(/g, 100]],
|
|
444
|
+
["rs", [/^\s+(use|fn|mut|match)\b/gm, 100]],
|
|
445
|
+
["go", [/\b(func|fmt|package)\b/g, 100]],
|
|
446
|
+
["java", [/^import\s+java/gm, 500]],
|
|
447
|
+
["asm", [/^(section|global main|extern|\t(call|mov|ret))/gm, 100]],
|
|
448
|
+
["css", [/^(@import|@page|@media|(\.|#)[a-z]+)/gm, 20]],
|
|
449
|
+
["json", [/\b(true|false|null|\{})\b|\"[^"]+\":/g, 10]],
|
|
450
|
+
["yaml", [/^(\s+)?[a-z][a-z0-9]*:/gim, 10]]
|
|
451
|
+
];
|
|
452
|
+
var detectLanguage = (code) => {
|
|
453
|
+
return languages.map(
|
|
454
|
+
([lang, ...features]) => [lang, features.reduce((acc, [match, score]) => acc + [...code.matchAll(match)].length * score, 0)]
|
|
455
|
+
).filter(([_, score]) => score > 20).sort((a, b) => b[1] - a[1])[0]?.[0] || "plain";
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// src/mdast-handlers/custom-code-handler.ts
|
|
459
|
+
var LANGUAGE_MATCH_REGEX = [/language-(\w+)/, /highlight-source-(\w+)/, /CodeBlock--language-(\w+)/];
|
|
460
|
+
var customCodeHandler = (state, node) => {
|
|
461
|
+
const classNames = node.properties?.className || [];
|
|
462
|
+
const codeValue = trimTrailingLines(toText(node)).trim();
|
|
463
|
+
const classLang = classNames.map((className) => {
|
|
464
|
+
const match = LANGUAGE_MATCH_REGEX.map((regex) => className.match(regex)).find((match2) => match2);
|
|
465
|
+
return match?.[1];
|
|
466
|
+
}).find((className) => className);
|
|
467
|
+
const lang = classLang || detectLanguage(codeValue) || null;
|
|
468
|
+
const result = { type: "code", lang, meta: null, value: codeValue };
|
|
469
|
+
state.patch(node, result);
|
|
470
|
+
return result;
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// src/mdast-handlers/custom-div-handler.ts
|
|
474
|
+
import { select as select3 } from "hast-util-select";
|
|
475
|
+
import { defaultHandlers as defaultHandlers2 } from "hast-util-to-mdast";
|
|
476
|
+
import { toString as hastToString3 } from "hast-util-to-string";
|
|
477
|
+
import { toText as toText2 } from "hast-util-to-text";
|
|
478
|
+
import { trimTrailingLines as trimTrailingLines2 } from "trim-trailing-lines";
|
|
479
|
+
var CODE_BLOCK_REGEX = /highlight-source|language-|codegroup|codeblock|code-block/i;
|
|
480
|
+
var CODE_FILENAME_SELECTORS = "[class*='fileName'],[class*='fileName'],[class*='title'],[class*='Title']";
|
|
481
|
+
var LANGUAGE_MATCH_REGEX2 = [/language-(\w+)/, /highlight-source-(\w+)/, /CodeBlock--language-(\w+)/];
|
|
482
|
+
var findRecursive = (array, condition, maxDepth = 3) => {
|
|
483
|
+
if (maxDepth <= 0) {
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
for (const value of array) {
|
|
487
|
+
const result = condition(value);
|
|
488
|
+
if (Array.isArray(result)) {
|
|
489
|
+
return findRecursive(result, condition, maxDepth - 1);
|
|
490
|
+
}
|
|
491
|
+
if (result) {
|
|
492
|
+
return value;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return null;
|
|
496
|
+
};
|
|
497
|
+
var customDivHandler = (state, node) => {
|
|
498
|
+
const classNames = Array.isArray(node.properties.className) ? node.properties.className : [];
|
|
499
|
+
const codeBlock = findRecursive(node.children, (child) => {
|
|
500
|
+
if (child.type !== "element") {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
if (child.tagName === "pre") {
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
return child.children.filter((child2) => child2.type === "element");
|
|
507
|
+
});
|
|
508
|
+
if (codeBlock && classNames.some((className) => CODE_BLOCK_REGEX.test(className))) {
|
|
509
|
+
const codeBlockClassNames = codeBlock.type === "element" ? codeBlock.properties.className ?? [] : [];
|
|
510
|
+
const codeValue = trimTrailingLines2(toText2(codeBlock)).trim();
|
|
511
|
+
const filenameElement = select3(CODE_FILENAME_SELECTORS, node);
|
|
512
|
+
const fileLang = filenameElement ? hastToString3(filenameElement).match(/\.(\w+)$/)?.[1] : null;
|
|
513
|
+
const classLang = [...classNames, ...codeBlockClassNames].map((className) => {
|
|
514
|
+
const match = LANGUAGE_MATCH_REGEX2.map((regex) => className.match(regex)).find((match2) => match2);
|
|
515
|
+
return match?.[1];
|
|
516
|
+
}).find((className) => className);
|
|
517
|
+
const lang = fileLang || classLang || detectLanguage(codeValue) || null;
|
|
518
|
+
const result = { type: "code", lang, meta: null, value: codeValue };
|
|
519
|
+
state.patch(node, result);
|
|
520
|
+
return result;
|
|
521
|
+
}
|
|
522
|
+
return defaultHandlers2.div(state, node);
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
// src/mdast-handlers/custom-img-handler.ts
|
|
526
|
+
import { defaultHandlers as defaultHandlers3 } from "hast-util-to-mdast";
|
|
527
|
+
var customImgHandler = (options) => (state, node) => {
|
|
528
|
+
if (options?.hideImage) {
|
|
529
|
+
return void 0;
|
|
530
|
+
}
|
|
531
|
+
return defaultHandlers3.image(state, node);
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// src/mdast-handlers/custom-table-handler.ts
|
|
535
|
+
import { defaultHandlers as defaultHandlers4 } from "hast-util-to-mdast";
|
|
536
|
+
import { toText as toText3 } from "hast-util-to-text";
|
|
537
|
+
var customTableHandler = (options) => (state, node) => {
|
|
538
|
+
if (options?.asText) {
|
|
539
|
+
const paragraph = { type: "paragraph", children: [{ type: "text", value: toText3(node) }] };
|
|
540
|
+
state.patch(node, paragraph);
|
|
541
|
+
return paragraph;
|
|
542
|
+
}
|
|
543
|
+
return defaultHandlers4.table(state, node);
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
// src/mdast-handlers/math-handler.ts
|
|
547
|
+
import { toHtml } from "hast-util-to-html";
|
|
548
|
+
import { MathMLToLaTeX } from "mathml-to-latex";
|
|
549
|
+
var mathHandler = (state, node) => {
|
|
550
|
+
const mathMl = toHtml(node);
|
|
551
|
+
const latex = MathMLToLaTeX.convert(mathMl);
|
|
552
|
+
const result = { type: "inlineMath", value: latex };
|
|
553
|
+
state.patch(node, result);
|
|
554
|
+
return result;
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
// src/utils/hast-utils.ts
|
|
558
|
+
import { select as select4, selectAll as selectAll2 } from "hast-util-select";
|
|
559
|
+
var getLangFromHast = (node) => {
|
|
560
|
+
const html = select4("html", node);
|
|
561
|
+
if (html && typeof html.properties.lang === "string") {
|
|
562
|
+
return html.properties.lang;
|
|
563
|
+
}
|
|
564
|
+
if (node.type !== "element") {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const element = node;
|
|
568
|
+
if (element.tagName !== "html") {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
const langAttr = element.properties.lang || element.properties["xml:lang"];
|
|
572
|
+
if (langAttr) {
|
|
573
|
+
return langAttr;
|
|
574
|
+
}
|
|
575
|
+
return void 0;
|
|
576
|
+
};
|
|
577
|
+
var getLangFromStr = (str) => {
|
|
578
|
+
const match = str.match(/lang=["']([^"']+)["']/);
|
|
579
|
+
if (match) {
|
|
580
|
+
return match[1];
|
|
581
|
+
}
|
|
582
|
+
return void 0;
|
|
583
|
+
};
|
|
584
|
+
var getUrlFromHast = (node) => {
|
|
585
|
+
if (node.type !== "element") {
|
|
586
|
+
return void 0;
|
|
587
|
+
}
|
|
588
|
+
const metaTagAttributes = ["og:url", "twitter:url"];
|
|
589
|
+
const metaTags = selectAll2("meta", node);
|
|
590
|
+
for (const meta of metaTags) {
|
|
591
|
+
const property = meta.properties.property || meta.properties.name;
|
|
592
|
+
if (typeof property === "string" && metaTagAttributes.includes(property)) {
|
|
593
|
+
return typeof meta.properties.content === "string" ? meta.properties.content : void 0;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return void 0;
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
// src/html-to-mdast.ts
|
|
600
|
+
var htmlToMdast = (htmlOrHast, options) => {
|
|
601
|
+
const { extractors, url: defaultUrl, lang: defaultLang } = options || {};
|
|
602
|
+
const [lang, hast] = typeof htmlOrHast === "string" ? [defaultLang || getLangFromStr(htmlOrHast), fromHtml(htmlOrHast, { fragment: true })] : [defaultLang || getLangFromHast(htmlOrHast), htmlOrHast];
|
|
603
|
+
const url = defaultUrl || getUrlFromHast(hast);
|
|
604
|
+
const extractedHast = extractHast({ hast, lang, url }, extractors);
|
|
605
|
+
const mdast = toMdast(extractedHast, {
|
|
606
|
+
handlers: {
|
|
607
|
+
math: mathHandler,
|
|
608
|
+
div: customDivHandler,
|
|
609
|
+
pre: customCodeHandler,
|
|
610
|
+
a: customAHandler({ asText: options?.linkAsText }),
|
|
611
|
+
img: customImgHandler({ hideImage: options?.hideImage }),
|
|
612
|
+
table: customTableHandler({ asText: options?.tableAsText })
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
const extractedMdast = extractMdast(mdast);
|
|
616
|
+
return extractedMdast;
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
// src/mdast-to-markdown.ts
|
|
620
|
+
import { gfmToMarkdown } from "mdast-util-gfm";
|
|
621
|
+
import { mathToMarkdown } from "mdast-util-math";
|
|
622
|
+
import { toMarkdown } from "mdast-util-to-markdown";
|
|
623
|
+
|
|
624
|
+
// src/link-replacer.ts
|
|
625
|
+
var linkReplacer = (markdown, base) => {
|
|
626
|
+
const regex = /(!?\[.*?\]\()([^)\s]+)(\))/g;
|
|
627
|
+
return markdown.replace(regex, (match, pre, url, post) => {
|
|
628
|
+
if (/^(https?:|#)/.test(url)) {
|
|
629
|
+
return match;
|
|
41
630
|
}
|
|
42
631
|
try {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
632
|
+
const absoluteUrl = new URL(url, base).href;
|
|
633
|
+
return `${pre}${absoluteUrl}${post}`;
|
|
634
|
+
} catch {
|
|
635
|
+
return match;
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
// src/utils/mdast-utils.ts
|
|
641
|
+
var warpRoot = (mdast) => {
|
|
642
|
+
if (Array.isArray(mdast)) {
|
|
643
|
+
return { type: "root", children: mdast };
|
|
644
|
+
}
|
|
645
|
+
return mdast;
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
// src/mdast-to-markdown.ts
|
|
649
|
+
var DEFAULT_MDAST_TO_MARKDOWN_OPTIONS = {
|
|
650
|
+
extensions: [gfmToMarkdown(), mathToMarkdown()],
|
|
651
|
+
bullet: "-"
|
|
652
|
+
};
|
|
653
|
+
var mdastToMarkdown = (mdast, options) => {
|
|
654
|
+
const { baseUrl, ...toMarkdownOptions } = { ...DEFAULT_MDAST_TO_MARKDOWN_OPTIONS, ...options };
|
|
655
|
+
let markdown = toMarkdown(warpRoot(mdast), toMarkdownOptions).replace(/\*\*\*\*/g, "");
|
|
656
|
+
if (baseUrl) {
|
|
657
|
+
markdown = linkReplacer(markdown, baseUrl);
|
|
658
|
+
}
|
|
659
|
+
return markdown;
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
// src/html-to-markdown.ts
|
|
663
|
+
var htmlToMarkdown = (htmlOrHast, options) => {
|
|
664
|
+
const { baseUrl, formatting: toMarkdownOptions, ...toMdastOptions } = options || {};
|
|
665
|
+
const mdast = htmlToMdast(htmlOrHast, toMdastOptions);
|
|
666
|
+
const markdown = mdastToMarkdown(mdast, { baseUrl, ...toMarkdownOptions });
|
|
667
|
+
return markdown;
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
// src/cli/helpers/inputOutputPath.ts
|
|
671
|
+
import fs2 from "node:fs";
|
|
672
|
+
import { confirm, text } from "@clack/prompts";
|
|
673
|
+
|
|
674
|
+
// src/cli/utils.ts
|
|
675
|
+
import fs from "node:fs";
|
|
676
|
+
import path from "node:path";
|
|
677
|
+
var isUrl = (maybeUrl) => {
|
|
678
|
+
try {
|
|
679
|
+
new URL(maybeUrl);
|
|
680
|
+
return true;
|
|
681
|
+
} catch {
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
function changeFileExtension(filePath, newExtension) {
|
|
686
|
+
const parsed = path.parse(filePath);
|
|
687
|
+
const ext = newExtension.startsWith(".") ? newExtension : `.${newExtension}`;
|
|
688
|
+
return path.format({ ...parsed, ext });
|
|
689
|
+
}
|
|
690
|
+
function urlToFilename(url) {
|
|
691
|
+
try {
|
|
692
|
+
const urlObj = new URL(url);
|
|
693
|
+
const hostname = urlObj.hostname.startsWith("www.") ? urlObj.hostname.slice(4) : urlObj.hostname;
|
|
694
|
+
const domainParts = hostname.split(".").slice(-2).join("-");
|
|
695
|
+
const pathParts = urlObj.pathname.split("/").filter(Boolean);
|
|
696
|
+
const relevantPathParts = pathParts.slice(-2).map(decodeURIComponent).join("-");
|
|
697
|
+
let filename = [domainParts, relevantPathParts].filter(Boolean).join("-");
|
|
698
|
+
filename = filename.toLowerCase().replace(/[<>:"/\\|?*\x00-\x1F]/g, "").replace(/[\s.]+/g, "-").replace(/^-+|-+$/g, "");
|
|
699
|
+
return filename || "output";
|
|
700
|
+
} catch {
|
|
701
|
+
return "output";
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
var sourcePathToOutputPath = (sourcePath) => isUrl(sourcePath) ? `${urlToFilename(sourcePath)}.md` : changeFileExtension(sourcePath, "md");
|
|
705
|
+
function getNextAvailableFilePath(filePath) {
|
|
706
|
+
const parsed = path.parse(filePath);
|
|
707
|
+
const dir = parsed.dir;
|
|
708
|
+
const ext = parsed.ext;
|
|
709
|
+
const baseName = parsed.name.replace(/_\d+$/, "");
|
|
710
|
+
const match = parsed.name.match(/_(\d+)$/);
|
|
711
|
+
let counter = match ? Number(match[1]) + 1 : 1;
|
|
712
|
+
let newPath = path.join(dir, `${baseName}_${counter}${ext}`);
|
|
713
|
+
while (fs.existsSync(newPath)) {
|
|
714
|
+
counter++;
|
|
715
|
+
newPath = path.join(dir, `${baseName}_${counter}${ext}`);
|
|
716
|
+
}
|
|
717
|
+
return newPath;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// src/cli/helpers/assertContinue.ts
|
|
721
|
+
import { cancel, isCancel } from "@clack/prompts";
|
|
722
|
+
function assertContinue(message, cancelMessage = "Canceled.") {
|
|
723
|
+
if (isCancel(message)) {
|
|
724
|
+
cancel(cancelMessage);
|
|
725
|
+
process.exit(1);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// src/cli/helpers/inputOutputPath.ts
|
|
730
|
+
var inputOutputPath = async (sourcePath) => {
|
|
731
|
+
const outputPath = await text({
|
|
732
|
+
message: "Enter the output file path:",
|
|
733
|
+
placeholder: "output.md",
|
|
734
|
+
initialValue: sourcePathToOutputPath(sourcePath),
|
|
735
|
+
validate: (value) => {
|
|
736
|
+
if (value.trim() === "") {
|
|
737
|
+
return "Output path is required";
|
|
132
738
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
options: MODES.map((mode) => ({ value: mode, label: mode })),
|
|
136
|
-
initialValue: options.mode && MODES.includes(options.mode) ? options.mode : "default"
|
|
137
|
-
});
|
|
138
|
-
if (isCancel(finalMode)) {
|
|
139
|
-
cancel("Canceled.");
|
|
140
|
-
return;
|
|
739
|
+
if (fs2.existsSync(value) && fs2.statSync(value).isDirectory()) {
|
|
740
|
+
return "No directory can be specified.";
|
|
141
741
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
console.error(pc.red("An error occurred while processing the given value:"), error.message);
|
|
145
|
-
if (process.env.DEBUG) {
|
|
146
|
-
console.error(error.stack);
|
|
742
|
+
if (!fs2.existsSync(value) && value.endsWith("/")) {
|
|
743
|
+
return "No directory can be specified.";
|
|
147
744
|
}
|
|
148
|
-
process.exit(1);
|
|
149
745
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (!fs.existsSync(finalSource)) {
|
|
171
|
-
console.error(pc.red("The file does not exist"));
|
|
172
|
-
process.exit(1);
|
|
173
|
-
}
|
|
174
|
-
const stats = fs.statSync(finalSource);
|
|
175
|
-
if (!stats.isFile()) {
|
|
176
|
-
console.error(pc.red("The source must be a file"));
|
|
177
|
-
process.exit(1);
|
|
178
|
-
}
|
|
179
|
-
content = fs.readFileSync(finalSource, "utf-8");
|
|
180
|
-
loadSpinner.stop("Content retrieval is complete!");
|
|
746
|
+
});
|
|
747
|
+
assertContinue(outputPath);
|
|
748
|
+
if (!fs2.existsSync(outputPath)) {
|
|
749
|
+
return outputPath;
|
|
750
|
+
}
|
|
751
|
+
const isOutputFileOverwrite = await confirm({
|
|
752
|
+
message: "The file already exists. Overwrite?",
|
|
753
|
+
initialValue: false
|
|
754
|
+
});
|
|
755
|
+
assertContinue(isOutputFileOverwrite);
|
|
756
|
+
if (isOutputFileOverwrite) {
|
|
757
|
+
return outputPath;
|
|
758
|
+
}
|
|
759
|
+
const escapedOutputPath = await text({
|
|
760
|
+
message: "Enter the output file path:",
|
|
761
|
+
placeholder: "output.md",
|
|
762
|
+
initialValue: getNextAvailableFilePath(outputPath),
|
|
763
|
+
validate: (value) => {
|
|
764
|
+
if (value.trim() === "") {
|
|
765
|
+
return "Output path is required";
|
|
181
766
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
console.error(error.stack);
|
|
767
|
+
if (fs2.existsSync(value)) {
|
|
768
|
+
return "The file already exists";
|
|
769
|
+
}
|
|
770
|
+
if (!fs2.existsSync(value) && value.endsWith("/")) {
|
|
771
|
+
return "No directory can be specified.";
|
|
188
772
|
}
|
|
189
|
-
process.exit(1);
|
|
190
|
-
}
|
|
191
|
-
if (process.env.DEBUG) {
|
|
192
|
-
console.info("Content:");
|
|
193
|
-
console.info(content);
|
|
194
773
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
774
|
+
});
|
|
775
|
+
assertContinue(escapedOutputPath);
|
|
776
|
+
return escapedOutputPath;
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
// src/cli/helpers/inputSourcePath.ts
|
|
780
|
+
import fs3 from "node:fs";
|
|
781
|
+
import { text as text2 } from "@clack/prompts";
|
|
782
|
+
|
|
783
|
+
// src/cli/constants.ts
|
|
784
|
+
var DEFAULT_PATH = "https://example.com";
|
|
785
|
+
var LOADERS = ["fetch", "playwright"];
|
|
786
|
+
var MODES = ["default", "ai"];
|
|
787
|
+
|
|
788
|
+
// src/cli/helpers/inputSourcePath.ts
|
|
789
|
+
var inputSourcePath = async () => {
|
|
790
|
+
const result = await text2({
|
|
791
|
+
message: "Enter the URL or html path to be converted to markdown:",
|
|
792
|
+
placeholder: DEFAULT_PATH,
|
|
793
|
+
initialValue: "",
|
|
794
|
+
validate: (value) => {
|
|
795
|
+
if (value.trim() === "") {
|
|
796
|
+
return "Source is required";
|
|
205
797
|
}
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
798
|
+
if (!isUrl(value)) {
|
|
799
|
+
if (!fs3.existsSync(value)) {
|
|
800
|
+
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.";
|
|
801
|
+
}
|
|
802
|
+
if (fs3.statSync(value).isDirectory()) {
|
|
803
|
+
return "You are specifying a local file, but you cannot specify a directory. hint: when specifying a url, start with http or https.";
|
|
210
804
|
}
|
|
211
|
-
fs.writeFileSync(finalOutputPath, markdown);
|
|
212
|
-
convertSpinner.stop("Markdown conversion is complete!");
|
|
213
|
-
outro(`Converted successfully to markdown! Output saved to: ${path.resolve(finalOutputPath)} \u{1F389}`);
|
|
214
|
-
} else {
|
|
215
|
-
convertSpinner.stop("Markdown conversion is complete!");
|
|
216
|
-
outro("Converted successfully to markdown\u{1F389}");
|
|
217
|
-
console.log(`
|
|
218
|
-
|
|
219
|
-
${markdown}`);
|
|
220
805
|
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
assertContinue(result);
|
|
809
|
+
return result;
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
// src/cli/helpers/selectExtractMode.ts
|
|
813
|
+
import { select as select5 } from "@clack/prompts";
|
|
814
|
+
var selectExtractMode = async () => {
|
|
815
|
+
const result = await select5({
|
|
816
|
+
message: "Select processing mode:",
|
|
817
|
+
options: MODES.map((mode) => ({ value: mode, label: mode })),
|
|
818
|
+
initialValue: MODES[0]
|
|
819
|
+
});
|
|
820
|
+
assertContinue(result);
|
|
821
|
+
return result;
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
// src/cli/helpers/selectLoader.ts
|
|
825
|
+
import { select as select6 } from "@clack/prompts";
|
|
826
|
+
var loadersHint = {
|
|
827
|
+
fetch: "Fetch HTML content from the given URL",
|
|
828
|
+
playwright: "Retrieve HTML content after rendering using Playwright; Playwright must be installed in advance.",
|
|
829
|
+
puppeteer: "Retrieve HTML content after rendering using Puppeteer; Puppeteer must be installed in advance."
|
|
830
|
+
};
|
|
831
|
+
var selectLoader = async () => {
|
|
832
|
+
const result = await select6({
|
|
833
|
+
message: "Select loader:",
|
|
834
|
+
initialValue: "fetch",
|
|
835
|
+
options: LOADERS.map((mode) => ({ value: mode, label: mode, hint: loadersHint[mode] || "" }))
|
|
836
|
+
});
|
|
837
|
+
assertContinue(result);
|
|
838
|
+
return result;
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
// src/cli/commands/webforai/loadHtml.ts
|
|
842
|
+
import fs4 from "node:fs/promises";
|
|
843
|
+
import pc from "picocolors";
|
|
844
|
+
import { chromium as chromium2 } from "playwright-core";
|
|
845
|
+
import playwrightPackageJson from "playwright-core/package.json";
|
|
846
|
+
|
|
847
|
+
// src/loaders/fetch.ts
|
|
848
|
+
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";
|
|
849
|
+
var loadHtml = async (url, userAgent = USER_AGENT) => {
|
|
850
|
+
const response = await fetch(url, { headers: { "User-Agent": userAgent } });
|
|
851
|
+
return response.text();
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
// src/loaders/playwright.ts
|
|
855
|
+
import { chromium, devices } from "playwright-core";
|
|
856
|
+
var SUPER_BYPASS_DEVICE = {
|
|
857
|
+
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
|
858
|
+
viewport: { width: 1920, height: 1080 },
|
|
859
|
+
deviceScaleFactor: 1,
|
|
860
|
+
hasTouch: false,
|
|
861
|
+
isMobile: false,
|
|
862
|
+
javaScriptEnabled: true,
|
|
863
|
+
locale: "en-US",
|
|
864
|
+
timezoneId: "America/New_York"
|
|
865
|
+
};
|
|
866
|
+
var loadHtml2 = async (url, options) => {
|
|
867
|
+
const { browser, waitUntil, timeout, superBypassMode } = options ?? {};
|
|
868
|
+
const _browser = browser ?? await chromium.launch({ headless: true });
|
|
869
|
+
const context = await _browser.newContext(superBypassMode ? SUPER_BYPASS_DEVICE : devices["Desktop Chrome"]);
|
|
870
|
+
if (superBypassMode) {
|
|
871
|
+
await context.addInitScript(() => {
|
|
872
|
+
Object.defineProperty(navigator, "webdriver", { get: () => void 0 });
|
|
873
|
+
Object.defineProperty(navigator, "languages", { get: () => ["en-US", "en"] });
|
|
874
|
+
Object.defineProperty(navigator, "plugins", { get: () => [1, 2, 3, 4, 5] });
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
const page = await context.newPage();
|
|
878
|
+
if (superBypassMode) {
|
|
879
|
+
await page.route("**/*.js", (route) => {
|
|
880
|
+
if (route.request().url().includes("captcha-delivery")) {
|
|
881
|
+
return route.abort();
|
|
227
882
|
}
|
|
228
|
-
|
|
883
|
+
return route.continue();
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
await page.goto(url, { waitUntil: waitUntil ?? "load", timeout });
|
|
887
|
+
const html = await page.content();
|
|
888
|
+
await page.close();
|
|
889
|
+
if (browser) {
|
|
890
|
+
await context.close();
|
|
891
|
+
} else {
|
|
892
|
+
await _browser.close();
|
|
893
|
+
}
|
|
894
|
+
return html;
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
// src/cli/commands/webforai/loadHtml.ts
|
|
898
|
+
var checkPlaywrightAvailable = async () => {
|
|
899
|
+
const path3 = chromium2.executablePath();
|
|
900
|
+
const isAvailable = await fs4.access(path3).catch(() => false);
|
|
901
|
+
return !!isAvailable;
|
|
902
|
+
};
|
|
903
|
+
var loadHtml3 = async (sourcePath, loader, options) => {
|
|
904
|
+
if (loader === "local") {
|
|
905
|
+
options.debug && console.debug(`Loading HTML from local file: ${sourcePath}`);
|
|
906
|
+
const content = await fs4.readFile(sourcePath, "utf-8");
|
|
907
|
+
options.debug && console.debug(`HTML loaded: ${content.slice(0, 100)}`);
|
|
908
|
+
return content;
|
|
909
|
+
}
|
|
910
|
+
if (loader === "fetch") {
|
|
911
|
+
options.debug && console.debug(`Loading HTML from URL: ${sourcePath}`);
|
|
912
|
+
const content = await loadHtml(sourcePath);
|
|
913
|
+
options.debug && console.debug(`HTML loaded: ${content.slice(0, 100)}`);
|
|
914
|
+
return content;
|
|
915
|
+
}
|
|
916
|
+
if (loader === "playwright") {
|
|
917
|
+
options.debug && console.debug(`Loading HTML from playwright: ${sourcePath}`);
|
|
918
|
+
const isPlaywrightAvailable = await checkPlaywrightAvailable();
|
|
919
|
+
options.debug && console.debug(`Playwright available: ${isPlaywrightAvailable}`);
|
|
920
|
+
if (!isPlaywrightAvailable) {
|
|
921
|
+
const message = [
|
|
922
|
+
"To use playwright loader, you need to install playwright.",
|
|
923
|
+
"You can install it by running:",
|
|
924
|
+
"",
|
|
925
|
+
` ${pc.bold(`npx playwright@${playwrightPackageJson.version} install chromium`)} `,
|
|
926
|
+
"",
|
|
927
|
+
"Hint1: If you get warning like this:",
|
|
928
|
+
"WARNING: It looks like you are running 'npx playwright install' without first installing your project's dependencies. ",
|
|
929
|
+
`${pc.bold("Ignore this warning.")}`,
|
|
930
|
+
"",
|
|
931
|
+
"Hint2: If you get message like this:",
|
|
932
|
+
"Host system is missing dependencies to run browsers.",
|
|
933
|
+
"You should install dependencies by running:",
|
|
934
|
+
"",
|
|
935
|
+
` ${pc.bold(`sudo npx playwright@${playwrightPackageJson.version} install-deps`)} `,
|
|
936
|
+
"",
|
|
937
|
+
pc.gray("Note: Good luck with that.")
|
|
938
|
+
];
|
|
939
|
+
console.info(message.join("\n"));
|
|
940
|
+
throw new Error("Playwright is not available");
|
|
229
941
|
}
|
|
942
|
+
const content = await loadHtml2(sourcePath);
|
|
943
|
+
options.debug && console.debug(`HTML loaded: ${content.slice(0, 100)}`);
|
|
944
|
+
return content;
|
|
945
|
+
}
|
|
946
|
+
throw new Error(`Unsupported loader: ${loader}`);
|
|
947
|
+
};
|
|
948
|
+
|
|
949
|
+
// src/cli/commands/webforai/index.ts
|
|
950
|
+
var aiModeOptions = { linkAsText: true, tableAsText: true, hideImage: true };
|
|
951
|
+
var readabilityModeOptions = { linkAsText: false, tableAsText: false, hideImage: false };
|
|
952
|
+
var webforaiCommand = async (initialPath, options) => {
|
|
953
|
+
intro(pc2.bold(pc2.green(`webforai CLI version ${package_default.version}`)));
|
|
954
|
+
const sourcePath = initialPath ?? await inputSourcePath();
|
|
955
|
+
options.debug && console.debug(`sourcePath: ${sourcePath}`);
|
|
956
|
+
const loader = isUrl(sourcePath) ? options.loader ?? await selectLoader() : "local";
|
|
957
|
+
options.debug && console.debug(`loader: ${loader}`);
|
|
958
|
+
const outputPath = options.output ?? await inputOutputPath(sourcePath);
|
|
959
|
+
options.debug && console.debug(`outputPath: ${outputPath}`);
|
|
960
|
+
const mode = options.mode ?? await selectExtractMode();
|
|
961
|
+
options.debug && console.debug(`mode: ${mode}`);
|
|
962
|
+
let html;
|
|
963
|
+
const s = spinner();
|
|
964
|
+
try {
|
|
965
|
+
s.start("Loading content...");
|
|
966
|
+
html = await loadHtml3(sourcePath, loader, { debug: options.debug });
|
|
967
|
+
s.stop(pc2.green("Content loaded!"));
|
|
968
|
+
} catch (error) {
|
|
969
|
+
s.stop(pc2.red("Content loading failed!"));
|
|
970
|
+
console.error(error);
|
|
971
|
+
process.exit(1);
|
|
230
972
|
}
|
|
231
|
-
);
|
|
232
|
-
|
|
973
|
+
options.debug && console.debug(`html: ${html}`);
|
|
974
|
+
const markdown = htmlToMarkdown(html, {
|
|
975
|
+
baseUrl: isUrl(sourcePath) ? sourcePath : void 0,
|
|
976
|
+
...mode === "ai" ? aiModeOptions : readabilityModeOptions
|
|
977
|
+
});
|
|
978
|
+
options.debug && console.debug(`markdown: ${markdown}`);
|
|
979
|
+
const directory = path2.dirname(outputPath);
|
|
980
|
+
const isDirectoryExists = await fs5.stat(directory).then((stat) => stat.isDirectory());
|
|
981
|
+
if (!isDirectoryExists) {
|
|
982
|
+
await fs5.mkdir(directory, { recursive: true });
|
|
983
|
+
}
|
|
984
|
+
await fs5.writeFile(outputPath, markdown);
|
|
985
|
+
outro(pc2.green(`${pc2.bold("Done!")} Markdown saved to ${outputPath}`));
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
// src/cli/bin.ts
|
|
989
|
+
program.name("webforai").description("CLI tool for ultra-precise HTML to Markdown conversion").version(package_default.version, "-v, --version", "output the current version");
|
|
990
|
+
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);
|
|
991
|
+
intro2(pc3.inverse(`webforai CLI version ${package_default.version}`));
|
|
233
992
|
program.parse();
|