webforai 2.1.1 → 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.
Files changed (203) hide show
  1. package/README.md +86 -4
  2. package/dist/adapters/index.js +58 -0
  3. package/dist/adapters/registry.js +149 -0
  4. package/dist/adapters/sites/declarative.js +178 -0
  5. package/dist/adapters/sites/hackernews.js +94 -0
  6. package/dist/adapters/sites/youtube.js +165 -0
  7. package/dist/adapters/types.js +0 -0
  8. package/dist/bin.js +4142 -549
  9. package/dist/cjs/adapters/index.js +86 -0
  10. package/dist/cjs/adapters/registry.js +178 -0
  11. package/dist/cjs/adapters/sites/declarative.js +202 -0
  12. package/dist/cjs/adapters/sites/hackernews.js +117 -0
  13. package/dist/cjs/adapters/sites/youtube.js +191 -0
  14. package/dist/cjs/adapters/types.js +16 -0
  15. package/dist/cjs/constants.js +2 -2
  16. package/dist/cjs/detect-client-shell.js +78 -0
  17. package/dist/cjs/extract-meta-refresh.js +71 -0
  18. package/dist/cjs/extractors/index.js +5 -0
  19. package/dist/cjs/extractors/lib/constants.js +186 -0
  20. package/dist/cjs/extractors/lib/sanitize.js +429 -0
  21. package/dist/cjs/extractors/lib/score.js +264 -0
  22. package/dist/cjs/extractors/lib/terminators.js +131 -0
  23. package/dist/cjs/extractors/lib/ui-chrome.js +119 -0
  24. package/dist/cjs/extractors/pipeExtractors.js +12 -9
  25. package/dist/cjs/extractors/presets/auto.js +44 -0
  26. package/dist/cjs/extractors/presets/minimal-filter.js +27 -48
  27. package/dist/cjs/extractors/presets/takumi.js +84 -122
  28. package/dist/cjs/html-to-markdown.js +44 -7
  29. package/dist/cjs/html-to-mdast.js +10 -1
  30. package/dist/cjs/index.js +43 -2
  31. package/dist/cjs/link-replacer.js +19 -13
  32. package/dist/cjs/loaders/cf-puppeteer.js +14 -7
  33. package/dist/cjs/loaders/fetch.js +14 -2
  34. package/dist/cjs/loaders/playwright.js +32 -30
  35. package/dist/cjs/loaders/puppeteer.js +14 -7
  36. package/dist/cjs/md-splitter.js +24 -11
  37. package/dist/cjs/mdast-handlers/custom-a-handler.js +25 -9
  38. package/dist/cjs/mdast-handlers/custom-code-handler.js +18 -7
  39. package/dist/cjs/mdast-handlers/custom-div-handler.js +12 -33
  40. package/dist/cjs/mdast-handlers/custom-img-handler.js +23 -1
  41. package/dist/cjs/mdast-handlers/custom-table-handler.js +16 -0
  42. package/dist/cjs/mdast-handlers/definition-list-handler.js +110 -0
  43. package/dist/cjs/mdast-handlers/math-handler.js +12 -3
  44. package/dist/cjs/mdast-handlers/sup-sub-handler.js +39 -0
  45. package/dist/cjs/mdast-to-markdown.js +2 -5
  46. package/dist/cjs/metadata/index.js +223 -0
  47. package/dist/cjs/normalize/index.js +177 -0
  48. package/dist/cjs/platform/client.js +204 -0
  49. package/dist/cjs/platform/error.js +36 -0
  50. package/dist/cjs/platform/fetch.js +37 -0
  51. package/dist/cjs/platform/index.js +44 -0
  52. package/dist/cjs/platform/types.js +40 -0
  53. package/dist/cjs/utils/hast-fast.js +284 -0
  54. package/dist/constants.js +2 -2
  55. package/dist/detect-client-shell.js +53 -0
  56. package/dist/extract-meta-refresh.js +47 -0
  57. package/dist/extractors/index.js +3 -0
  58. package/dist/extractors/lib/constants.js +150 -0
  59. package/dist/extractors/lib/sanitize.js +419 -0
  60. package/dist/extractors/lib/score.js +242 -0
  61. package/dist/extractors/lib/terminators.js +107 -0
  62. package/dist/extractors/lib/ui-chrome.js +96 -0
  63. package/dist/extractors/pipeExtractors.js +12 -9
  64. package/dist/extractors/presets/auto.js +20 -0
  65. package/dist/extractors/presets/minimal-filter.js +35 -47
  66. package/dist/extractors/presets/takumi.js +91 -122
  67. package/dist/html-to-markdown.js +42 -6
  68. package/dist/html-to-mdast.js +10 -1
  69. package/dist/index.js +45 -2
  70. package/dist/link-replacer.js +19 -13
  71. package/dist/loaders/cf-puppeteer.js +15 -8
  72. package/dist/loaders/fetch.js +14 -2
  73. package/dist/loaders/playwright.js +32 -30
  74. package/dist/loaders/puppeteer.js +15 -8
  75. package/dist/md-splitter.js +24 -11
  76. package/dist/mdast-handlers/custom-a-handler.js +25 -9
  77. package/dist/mdast-handlers/custom-code-handler.js +17 -7
  78. package/dist/mdast-handlers/custom-div-handler.js +12 -33
  79. package/dist/mdast-handlers/custom-img-handler.js +23 -1
  80. package/dist/mdast-handlers/custom-table-handler.js +16 -0
  81. package/dist/mdast-handlers/definition-list-handler.js +87 -0
  82. package/dist/mdast-handlers/math-handler.js +12 -3
  83. package/dist/mdast-handlers/sup-sub-handler.js +16 -0
  84. package/dist/mdast-to-markdown.js +2 -5
  85. package/dist/metadata/index.js +198 -0
  86. package/dist/normalize/index.js +159 -0
  87. package/dist/platform/client.js +182 -0
  88. package/dist/platform/error.js +13 -0
  89. package/dist/platform/fetch.js +14 -0
  90. package/dist/platform/index.js +23 -0
  91. package/dist/platform/types.js +13 -0
  92. package/dist/types/adapters/index.d.ts +24 -0
  93. package/dist/types/adapters/index.js.map +1 -0
  94. package/dist/types/adapters/registry.d.ts +35 -0
  95. package/dist/types/adapters/registry.js.map +1 -0
  96. package/dist/types/adapters/sites/declarative.d.ts +13 -0
  97. package/dist/types/adapters/sites/declarative.js.map +1 -0
  98. package/dist/types/adapters/sites/hackernews.d.ts +13 -0
  99. package/dist/types/adapters/sites/hackernews.js.map +1 -0
  100. package/dist/types/adapters/sites/youtube.d.ts +31 -0
  101. package/dist/types/adapters/sites/youtube.js.map +1 -0
  102. package/dist/types/adapters/types.d.ts +99 -0
  103. package/dist/types/adapters/types.js.map +1 -0
  104. package/dist/types/cli/bin.js.map +1 -1
  105. package/dist/types/cli/commands/jobs/files.d.ts +28 -0
  106. package/dist/types/cli/commands/jobs/files.js.map +1 -0
  107. package/dist/types/cli/commands/jobs/llms-txt.d.ts +33 -0
  108. package/dist/types/cli/commands/jobs/llms-txt.js.map +1 -0
  109. package/dist/types/cli/commands/jobs/options.d.ts +58 -0
  110. package/dist/types/cli/commands/jobs/options.js.map +1 -0
  111. package/dist/types/cli/commands/jobs/run.d.ts +44 -0
  112. package/dist/types/cli/commands/jobs/run.js.map +1 -0
  113. package/dist/types/cli/commands/skill/content.d.ts +9 -0
  114. package/dist/types/cli/commands/skill/content.js.map +1 -0
  115. package/dist/types/cli/commands/skill/index.d.ts +19 -0
  116. package/dist/types/cli/commands/skill/index.js.map +1 -0
  117. package/dist/types/cli/commands/webforai/interactive.d.ts +7 -0
  118. package/dist/types/cli/commands/webforai/interactive.js.map +1 -0
  119. package/dist/types/cli/commands/webforai/loadHtml.js.map +1 -1
  120. package/dist/types/cli/commands/webforai/options.d.ts +62 -0
  121. package/dist/types/cli/commands/webforai/options.js.map +1 -0
  122. package/dist/types/cli/commands/webforai/run.d.ts +10 -0
  123. package/dist/types/cli/commands/webforai/run.js.map +1 -0
  124. package/dist/types/cli/constants.d.ts +14 -4
  125. package/dist/types/cli/constants.js.map +1 -1
  126. package/dist/types/cli/helpers/selectExtractMode.js.map +1 -1
  127. package/dist/types/cli/helpers/selectLoader.js.map +1 -1
  128. package/dist/types/cli/utils.d.ts +8 -1
  129. package/dist/types/cli/utils.js.map +1 -1
  130. package/dist/types/constants.d.ts +7 -1
  131. package/dist/types/constants.js.map +1 -1
  132. package/dist/types/detect-client-shell.d.ts +24 -0
  133. package/dist/types/detect-client-shell.js.map +1 -0
  134. package/dist/types/extract-meta-refresh.d.ts +29 -0
  135. package/dist/types/extract-meta-refresh.js.map +1 -0
  136. package/dist/types/extractors/index.d.ts +1 -0
  137. package/dist/types/extractors/index.js.map +1 -1
  138. package/dist/types/extractors/lib/constants.d.ts +81 -0
  139. package/dist/types/extractors/lib/constants.js.map +1 -0
  140. package/dist/types/extractors/lib/sanitize.d.ts +66 -0
  141. package/dist/types/extractors/lib/sanitize.js.map +1 -0
  142. package/dist/types/extractors/lib/score.d.ts +85 -0
  143. package/dist/types/extractors/lib/score.js.map +1 -0
  144. package/dist/types/extractors/lib/terminators.d.ts +26 -0
  145. package/dist/types/extractors/lib/terminators.js.map +1 -0
  146. package/dist/types/extractors/lib/ui-chrome.d.ts +18 -0
  147. package/dist/types/extractors/lib/ui-chrome.js.map +1 -0
  148. package/dist/types/extractors/pipeExtractors.d.ts +5 -0
  149. package/dist/types/extractors/pipeExtractors.js.map +1 -1
  150. package/dist/types/extractors/presets/auto.d.ts +30 -0
  151. package/dist/types/extractors/presets/auto.js.map +1 -0
  152. package/dist/types/extractors/presets/minimal-filter.d.ts +10 -2
  153. package/dist/types/extractors/presets/minimal-filter.js.map +1 -1
  154. package/dist/types/extractors/presets/takumi.d.ts +19 -3
  155. package/dist/types/extractors/presets/takumi.js.map +1 -1
  156. package/dist/types/extractors/types.d.ts +8 -0
  157. package/dist/types/html-to-markdown.d.ts +38 -0
  158. package/dist/types/html-to-markdown.js.map +1 -1
  159. package/dist/types/html-to-mdast.d.ts +11 -0
  160. package/dist/types/html-to-mdast.js.map +1 -1
  161. package/dist/types/index.d.ts +8 -3
  162. package/dist/types/index.js.map +1 -1
  163. package/dist/types/link-replacer.d.ts +3 -1
  164. package/dist/types/link-replacer.js.map +1 -1
  165. package/dist/types/loaders/cf-puppeteer.js.map +1 -1
  166. package/dist/types/loaders/fetch.d.ts +4 -0
  167. package/dist/types/loaders/fetch.js.map +1 -1
  168. package/dist/types/loaders/playwright.js.map +1 -1
  169. package/dist/types/loaders/puppeteer.js.map +1 -1
  170. package/dist/types/md-splitter.d.ts +1 -1
  171. package/dist/types/md-splitter.js.map +1 -1
  172. package/dist/types/mdast-handlers/custom-a-handler.js.map +1 -1
  173. package/dist/types/mdast-handlers/custom-code-handler.d.ts +2 -0
  174. package/dist/types/mdast-handlers/custom-code-handler.js.map +1 -1
  175. package/dist/types/mdast-handlers/custom-div-handler.js.map +1 -1
  176. package/dist/types/mdast-handlers/custom-img-handler.js.map +1 -1
  177. package/dist/types/mdast-handlers/custom-table-handler.js.map +1 -1
  178. package/dist/types/mdast-handlers/definition-list-handler.d.ts +2 -0
  179. package/dist/types/mdast-handlers/definition-list-handler.js.map +1 -0
  180. package/dist/types/mdast-handlers/math-handler.js.map +1 -1
  181. package/dist/types/mdast-handlers/sup-sub-handler.d.ts +10 -0
  182. package/dist/types/mdast-handlers/sup-sub-handler.js.map +1 -0
  183. package/dist/types/mdast-to-markdown.js.map +1 -1
  184. package/dist/types/metadata/index.d.ts +47 -0
  185. package/dist/types/metadata/index.js.map +1 -0
  186. package/dist/types/normalize/index.d.ts +53 -0
  187. package/dist/types/normalize/index.js.map +1 -0
  188. package/dist/types/platform/client.d.ts +55 -0
  189. package/dist/types/platform/client.js.map +1 -0
  190. package/dist/types/platform/error.d.ts +22 -0
  191. package/dist/types/platform/error.js.map +1 -0
  192. package/dist/types/platform/fetch.d.ts +36 -0
  193. package/dist/types/platform/fetch.js.map +1 -0
  194. package/dist/types/platform/index.d.ts +4 -0
  195. package/dist/types/platform/index.js.map +1 -0
  196. package/dist/types/platform/types.d.ts +164 -0
  197. package/dist/types/platform/types.js.map +1 -0
  198. package/dist/types/utils/hast-fast.d.ts +101 -0
  199. package/dist/types/utils/hast-fast.js.map +1 -0
  200. package/dist/utils/hast-fast.js +246 -0
  201. package/package.json +25 -7
  202. package/dist/types/cli/commands/webforai/index.d.ts +0 -6
  203. package/dist/types/cli/commands/webforai/index.js.map +0 -1
@@ -1,134 +1,103 @@
1
1
  // src/extractors/presets/takumi.ts
2
- import { select, selectAll } from "hast-util-select";
3
- import { toString as hastToString } from "hast-util-to-string";
4
- import { filter } from "unist-util-filter";
5
- import { classnames, isStrInclude, matchString } from "./utils.js";
6
- var UNLIKELY_ROLES = ["menu", "menubar", "complementary", "navigation", "alert", "alertdialog", "dialog"];
7
- var REGEXPS = {
8
- hidden: /hidden|invisible|fallback-image/i,
9
- byline: /byline|author|dateline|writtenby|p-author/i,
10
- specialUnlikelyCandidates: /frb-|uls-menu|language-link/i,
11
- unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|tooltip|disqus|extra|footer|gdpr|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote|speechify-ignore|avatar/i,
12
- okMaybeItsaCandidate: /and|article|body|column|content|main|shadow|code/i
13
- };
14
- var BODY_SELECTORS = ["article", "#article", ".article_body", ".article-body", "#content", ".entry"];
15
- var PARAGRAPH_TAGS = ["a", "p", "div", "section", "article", "main", "ul", "ol", "li"];
16
- var CONTENTABLE_TAGS = ["article", "main", "section", "h1", "h2", "h3", "h4", "h5", "h6", "p"];
17
- var TEXTABLE_TAGS = ["p", "h1", "h2", "h3", "h4", "h5", "h6", "li", "ul"];
18
- var BASE_MINIMAL_LENGTH = { ja: 200, en: 500 };
19
- var metadataFilter = (node) => {
20
- return !(["comment", "doctype"].includes(node.type) || node.type === "element" && ["script", "style", "link", "meta", "noscript", "svg", "title"].includes(node.tagName));
21
- };
22
- var universalElementFilter = (node) => {
23
- if (node.type !== "element") {
24
- return true;
25
- }
26
- const element = node;
27
- if (["aside", "nav"].includes(element.tagName)) {
28
- return false;
29
- }
30
- if (["hidden", "aria-hidden"].some((key) => element.properties[key])) {
31
- return false;
32
- }
33
- if (classnames(element).some((classname) => REGEXPS.hidden.test(classname))) {
34
- return false;
35
- }
36
- if (element.tagName === "dialog") {
37
- return false;
38
- }
39
- if (element.properties.role === "dialog" && element.properties["aria-modal"]) {
40
- return false;
41
- }
42
- if (element.properties.rel === "author" && isStrInclude(element.properties.itemprop, "author")) {
43
- return false;
44
- }
45
- if (REGEXPS.byline.test(matchString(element))) {
46
- return false;
47
- }
48
- if (element.properties.role && UNLIKELY_ROLES.includes(element.properties.role)) {
49
- return false;
2
+ import {
3
+ MetricsCollector,
4
+ cloneHast,
5
+ findElement,
6
+ isElement,
7
+ pruneInPlace,
8
+ stringProperty
9
+ } from "../../utils/hast-fast.js";
10
+ import { minContentLength } from "../lib/constants.js";
11
+ import { cleanContent, findCleanupTargets, findUnlikelyElements, stripNonContent } from "../lib/sanitize.js";
12
+ import { buildScoringContext, climbToArticleBoundary, collectArticleNodes, scoreCandidates } from "../lib/score.js";
13
+ import { truncateTrailingBoilerplate } from "../lib/terminators.js";
14
+ var MIN_RETAINED_FRACTION = 0.25;
15
+ var SEMANTIC_SELECTORS = [
16
+ (element) => stringProperty(element, "itemprop")?.includes("articleBody") ?? false,
17
+ (element) => element.tagName === "article",
18
+ (element) => element.tagName === "main",
19
+ (element) => stringProperty(element, "role") === "main"
20
+ ];
21
+ var findBody = (hast) => findElement(hast, (element) => element.tagName === "body") ?? hast;
22
+ var semanticShortcut = (tree, collector, minLength) => {
23
+ for (const matches of SEMANTIC_SELECTORS) {
24
+ const found = [];
25
+ collectMatching(tree, matches, found);
26
+ if (found.length !== 1) {
27
+ continue;
28
+ }
29
+ const candidate = found[0];
30
+ const metrics = collector.metrics(candidate);
31
+ if (metrics.text >= minLength && collector.linkDensity(candidate) < 0.5) {
32
+ return candidate;
33
+ }
50
34
  }
51
- return true;
35
+ return void 0;
52
36
  };
53
- var unlikelyElementFilter = (node) => {
54
- if (node.type !== "element") {
55
- return true;
56
- }
57
- const element = node;
58
- if (CONTENTABLE_TAGS.includes(element.tagName)) {
59
- return true;
60
- }
61
- const match = matchString(element);
62
- if (REGEXPS.specialUnlikelyCandidates.test(match)) {
63
- return false;
64
- }
65
- if (REGEXPS.unlikelyCandidates.test(match) && !REGEXPS.okMaybeItsaCandidate.test(match)) {
66
- return false;
37
+ var collectMatching = (tree, matches, into) => {
38
+ const stack = [tree];
39
+ while (stack.length > 0 && into.length < 2) {
40
+ const node = stack.pop();
41
+ if (isElement(node) && matches(node)) {
42
+ into.push(node);
43
+ continue;
44
+ }
45
+ if ("children" in node) {
46
+ for (const child of node.children) {
47
+ stack.push(child);
48
+ }
49
+ }
67
50
  }
68
- return true;
69
51
  };
70
- var removeEmptyFilter = (node, _lang) => {
71
- if (node.type !== "element") {
72
- return true;
73
- }
74
- const element = node;
75
- if (PARAGRAPH_TAGS.includes(element.tagName)) {
76
- return true;
77
- }
78
- if (element.tagName === "img" && !element.properties.src) {
79
- return false;
80
- }
81
- if (TEXTABLE_TAGS.includes(element.tagName) && hastToString(element).length === 0) {
82
- return false;
83
- }
84
- return true;
52
+ var asRootOf = (nodes) => ({ type: "root", children: nodes });
53
+ var scoredSelection = (tree, collector, lang) => {
54
+ const context = buildScoringContext(tree, collector, lang);
55
+ const candidates = scoreCandidates(tree, context);
56
+ const top = candidates[0];
57
+ if (!top) {
58
+ return void 0;
59
+ }
60
+ const promoted = climbToArticleBoundary(top, candidates, context);
61
+ const anchor = promoted === top.element ? top : { element: promoted, score: top.score };
62
+ const selected = collectArticleNodes(anchor, candidates, context);
63
+ return selected.length === 1 ? selected[0] : asRootOf(selected);
85
64
  };
86
65
  var takumiExtractor = (params) => {
87
- const { hast, lang = "en" } = params;
88
- const body = select("body", hast) ?? hast;
89
- const metadataFilteredHast = filter(body, (node) => metadataFilter(node));
90
- const metadataFilteredHastText = metadataFilteredHast && hastToString(metadataFilteredHast);
91
- if (!(metadataFilteredHast && metadataFilteredHastText)) {
66
+ const { hast, lang, owned } = params;
67
+ const body = owned ? findBody(hast) : cloneHast(findBody(hast));
68
+ stripNonContent(body);
69
+ let collector = new MetricsCollector();
70
+ const baseText = collector.textLength(body);
71
+ if (baseText === 0) {
92
72
  return body;
93
73
  }
94
- const baseFilterd = filter(metadataFilteredHast, (node) => universalElementFilter(node));
95
- const baseFilterdText = baseFilterd ? hastToString(baseFilterd) : "";
96
- const [baseTree, baseText] = baseFilterdText.length > metadataFilteredHastText.length / 3 || baseFilterdText.length > 5e3 ? [baseFilterd, baseFilterdText] : [metadataFilteredHast, metadataFilteredHastText];
97
- let minimalLength = lang in BASE_MINIMAL_LENGTH ? BASE_MINIMAL_LENGTH[lang] : 500;
98
- if (baseText.length < minimalLength) {
99
- minimalLength = Math.max(0, baseText.length - 200);
100
- }
101
- let extractedTree = baseTree;
102
- let extractedText = baseText;
103
- for (const selector of BODY_SELECTORS) {
104
- const content = { type: "root", children: selectAll(selector, baseFilterd) };
105
- const contentText = hastToString(content);
106
- if (contentText.length < 25) {
107
- continue;
108
- }
109
- const links = selectAll("a", content);
110
- const linkText = links.map((link) => hastToString(link)).join("");
111
- const linkDensity = linkText.length / contentText.length;
112
- if (linkDensity > 0.4) {
113
- continue;
114
- }
115
- if (contentText.length > minimalLength) {
116
- extractedTree = content;
117
- extractedText = contentText;
118
- break;
119
- }
120
- }
121
- const finalFilteredTree = filter(extractedTree, (node) => {
122
- if (!removeEmptyFilter(node, lang)) {
123
- return false;
124
- }
125
- if (!unlikelyElementFilter(node)) {
126
- return false;
127
- }
128
- return true;
129
- });
130
- const finalTree = hastToString(finalFilteredTree).length > extractedText.length / 3 ? finalFilteredTree : extractedTree;
131
- return finalTree;
74
+ const minLength = Math.min(minContentLength(lang), Math.max(0, baseText - 200));
75
+ const shortcut = semanticShortcut(body, collector, minLength);
76
+ const searchRoot = shortcut ?? body;
77
+ const searchText = collector.textLength(searchRoot);
78
+ const doomed = findUnlikelyElements(searchRoot);
79
+ const doomedText = doomed.reduce((sum, element) => sum + collector.metrics(element).text, 0);
80
+ if (searchText - doomedText > Math.max(minLength, searchText * MIN_RETAINED_FRACTION)) {
81
+ const doomedSet = new Set(doomed);
82
+ pruneInPlace(searchRoot, (node) => !(isElement(node) && doomedSet.has(node)));
83
+ collector = new MetricsCollector();
84
+ }
85
+ const scoringRoot = searchRoot;
86
+ const scoringRootText = collector.textLength(scoringRoot);
87
+ const selection = shortcut ? scoringRoot : scoredSelection(scoringRoot, collector, lang) ?? scoringRoot;
88
+ const selectionText = collector.textLength(selection);
89
+ const selectionIsSound = selectionText >= minLength || selectionText > scoringRootText * MIN_RETAINED_FRACTION;
90
+ const chosen = selectionIsSound ? selection : scoringRoot;
91
+ truncateTrailingBoilerplate(chosen);
92
+ const cleanupCollector = new MetricsCollector();
93
+ const chosenText = cleanupCollector.textLength(chosen);
94
+ const widgets = findCleanupTargets(chosen, cleanupCollector);
95
+ const widgetText = widgets.reduce((sum, element) => sum + cleanupCollector.metrics(element).text, 0);
96
+ const remainingText = chosenText - widgetText;
97
+ if (remainingText < chosenText * MIN_RETAINED_FRACTION && remainingText < minLength) {
98
+ return chosen;
99
+ }
100
+ return cleanContent(chosen, cleanupCollector);
132
101
  };
133
102
  export {
134
103
  takumiExtractor
@@ -1,12 +1,48 @@
1
1
  // src/html-to-markdown.ts
2
+ import { fromHtml } from "hast-util-from-html";
2
3
  import { htmlToMdast } from "./html-to-mdast.js";
3
4
  import { mdastToMarkdown } from "./mdast-to-markdown.js";
4
- var htmlToMarkdown = (htmlOrHast, options) => {
5
- const { baseUrl, formatting: toMarkdownOptions, ...toMdastOptions } = options || {};
6
- const mdast = htmlToMdast(htmlOrHast, toMdastOptions);
7
- const markdown = mdastToMarkdown(mdast, { baseUrl, ...toMarkdownOptions });
8
- return markdown;
5
+ import { extractMetadata, toFrontmatter } from "./metadata/index.js";
6
+ import { getLangFromStr } from "./utils/hast-utils.js";
7
+ var htmlToMarkdownWithMetadata = (htmlOrHast, options) => {
8
+ const { baseUrl, frontmatter, title, formatting: toMarkdownOptions, ...toMdastOptions } = options || {};
9
+ const hast = typeof htmlOrHast === "string" ? fromHtml(htmlOrHast, { fragment: true }) : htmlOrHast;
10
+ const isOwned = typeof htmlOrHast === "string";
11
+ const declaredLang = typeof htmlOrHast === "string" ? getLangFromStr(htmlOrHast) : void 0;
12
+ const metadata = extractMetadata(hast);
13
+ if (declaredLang && !metadata.lang) {
14
+ metadata.lang = declaredLang;
15
+ }
16
+ const mdast = htmlToMdast(hast, {
17
+ ...toMdastOptions,
18
+ owned: isOwned,
19
+ lang: toMdastOptions.lang ?? metadata.lang,
20
+ url: toMdastOptions.url ?? metadata.canonicalUrl
21
+ });
22
+ const body = mdastToMarkdown(mdast, { baseUrl, ...toMarkdownOptions });
23
+ const titled = title === false ? body : withTitle(body, metadata.title);
24
+ const markdown = frontmatter ? `${toFrontmatter(metadata)}${titled}` : titled;
25
+ return { markdown, metadata };
9
26
  };
27
+ var TITLE_LOOKAHEAD = 400;
28
+ var withTitle = (body, title) => {
29
+ if (!title) {
30
+ return body;
31
+ }
32
+ const opening = body.slice(0, TITLE_LOOKAHEAD);
33
+ if (/^#\s+\S/m.test(opening)) {
34
+ return body;
35
+ }
36
+ if (normalizeForCompare(opening).includes(normalizeForCompare(title))) {
37
+ return body;
38
+ }
39
+ return `# ${title}
40
+
41
+ ${body}`;
42
+ };
43
+ var normalizeForCompare = (value) => value.replace(/\s+/g, " ").trim().toLowerCase();
44
+ var htmlToMarkdown = (htmlOrHast, options) => htmlToMarkdownWithMetadata(htmlOrHast, options).markdown;
10
45
  export {
11
- htmlToMarkdown
46
+ htmlToMarkdown,
47
+ htmlToMarkdownWithMetadata
12
48
  };
@@ -8,18 +8,27 @@ import { customCodeHandler } from "./mdast-handlers/custom-code-handler.js";
8
8
  import { customDivHandler } from "./mdast-handlers/custom-div-handler.js";
9
9
  import { customImgHandler } from "./mdast-handlers/custom-img-handler.js";
10
10
  import { customTableHandler } from "./mdast-handlers/custom-table-handler.js";
11
+ import { definitionListHandler } from "./mdast-handlers/definition-list-handler.js";
11
12
  import { mathHandler } from "./mdast-handlers/math-handler.js";
13
+ import { supSubHandler } from "./mdast-handlers/sup-sub-handler.js";
14
+ import { normalizeHast } from "./normalize/index.js";
15
+ import { cloneHast } from "./utils/hast-fast.js";
12
16
  import { getLangFromHast, getLangFromStr, getUrlFromHast } from "./utils/hast-utils.js";
13
17
  var htmlToMdast = (htmlOrHast, options) => {
14
18
  const { extractors, url: defaultUrl, lang: defaultLang } = options || {};
15
19
  const [lang, hast] = typeof htmlOrHast === "string" ? [defaultLang || getLangFromStr(htmlOrHast), fromHtml(htmlOrHast, { fragment: true })] : [defaultLang || getLangFromHast(htmlOrHast), htmlOrHast];
20
+ const isOwned = typeof htmlOrHast === "string" || (options?.owned ?? false);
16
21
  const url = defaultUrl || getUrlFromHast(hast);
17
- const extractedHast = pipeExtractors({ hast, lang, url }, extractors);
22
+ const extractedHast = pipeExtractors({ hast: isOwned ? hast : cloneHast(hast), lang, url, owned: true }, extractors);
23
+ normalizeHast(extractedHast, options?.normalize);
18
24
  const mdast = toMdast(extractedHast, {
19
25
  handlers: {
20
26
  math: mathHandler,
21
27
  div: customDivHandler,
22
28
  pre: customCodeHandler,
29
+ dl: definitionListHandler,
30
+ sup: supSubHandler("sup"),
31
+ sub: supSubHandler("sub"),
23
32
  a: customAHandler({ asText: options?.linkAsText }),
24
33
  img: customImgHandler({ hideImage: options?.hideImage }),
25
34
  table: customTableHandler({ asText: options?.tableAsText })
package/dist/index.js CHANGED
@@ -1,19 +1,62 @@
1
1
  // src/index.ts
2
- import { htmlToMarkdown } from "./html-to-markdown.js";
2
+ import {
3
+ htmlToMarkdown,
4
+ htmlToMarkdownWithMetadata
5
+ } from "./html-to-markdown.js";
3
6
  import { mdastSplitter } from "./md-splitter.js";
4
7
  import { htmlToMdast } from "./html-to-mdast.js";
5
8
  import { mdastToMarkdown } from "./mdast-to-markdown.js";
6
9
  import {
7
10
  pipeExtractors,
11
+ autoExtractor,
12
+ createAutoExtractor,
8
13
  takumiExtractor,
9
14
  minimalFilter
10
15
  } from "./extractors/index.js";
16
+ import {
17
+ BUILTIN_ADAPTERS,
18
+ adapterById,
19
+ buildPageSignature,
20
+ defineSelectorAdapter,
21
+ resolveAdapter,
22
+ runAdapters,
23
+ youtubeCaptionTracks
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";
34
+ import { extractMetadata, toFrontmatter } from "./metadata/index.js";
35
+ import { normalizeHast } from "./normalize/index.js";
11
36
  export {
37
+ BUILTIN_ADAPTERS,
38
+ MAX_META_REFRESH_DELAY_SECONDS,
39
+ SHELL_EMPTY_THRESHOLD,
40
+ SHELL_SUSPICION_THRESHOLD,
41
+ adapterById,
42
+ autoExtractor,
43
+ buildPageSignature,
44
+ createAutoExtractor,
45
+ defineSelectorAdapter,
46
+ detectClientShell,
47
+ extractMetaRefresh,
48
+ extractMetadata,
12
49
  htmlToMarkdown,
50
+ htmlToMarkdownWithMetadata,
13
51
  htmlToMdast,
14
52
  mdastSplitter,
15
53
  mdastToMarkdown,
16
54
  minimalFilter,
55
+ normalizeHast,
17
56
  pipeExtractors,
18
- takumiExtractor
57
+ resolveAdapter,
58
+ runAdapters,
59
+ takumiExtractor,
60
+ toFrontmatter,
61
+ youtubeCaptionTracks
19
62
  };
@@ -1,17 +1,23 @@
1
1
  // src/link-replacer.ts
2
- var linkReplacer = (markdown, base) => {
3
- const regex = /(!?\[.*?\]\()([^)\s]+)(\))/g;
4
- return markdown.replace(regex, (match, pre, url, post) => {
5
- if (/^(https?:|#)/.test(url)) {
6
- return match;
7
- }
8
- try {
9
- const absoluteUrl = new URL(url, base).href;
10
- return `${pre}${absoluteUrl}${post}`;
11
- } catch {
12
- return match;
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
- const page = await browser.newPage();
7
- await page.goto(url);
8
- const html = await page.content();
9
- await Promise.race([page.waitForNetworkIdle(), sleep(1e4)]);
10
- await page.close();
11
- return html;
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
@@ -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
- const response = await fetch(url, { headers: { "User-Agent": userAgent } });
5
- return response.text();
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
- const context = await _browser.newContext(superBypassMode ? SUPER_BYPASS_DEVICE : devices["Desktop Chrome"]);
17
- if (superBypassMode) {
18
- await context.addInitScript(() => {
19
- Object.defineProperty(navigator, "webdriver", { get: () => void 0 });
20
- Object.defineProperty(navigator, "languages", { get: () => ["en-US", "en"] });
21
- Object.defineProperty(navigator, "plugins", { get: () => [1, 2, 3, 4, 5] });
22
- });
23
- }
24
- const page = await context.newPage();
25
- if (superBypassMode) {
26
- await page.route("**/*.js", (route) => {
27
- if (route.request().url().includes("captcha-delivery")) {
28
- return route.abort();
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
- await page.goto(url, { waitUntil: waitUntil ?? "load", timeout });
34
- await page.evaluate(() => {
35
- const elements = document.querySelectorAll("*");
36
- for (const element of elements) {
37
- const rect = element.getBoundingClientRect();
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
- const page = await browser.newPage();
12
- await page.goto(url);
13
- const html = await page.content();
14
- await Promise.race([page.waitForNetworkIdle(), sleep(1e4)]);
15
- await browser.close();
16
- return html;
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
@@ -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 getSplitterGenerator = function* () {
7
- for (const splitter of PRIORITY_SPLITTERS) {
8
- yield splitter;
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
- if (await checker(markdown) || contents.length === 1) {
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, splitterGenerator));
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, _options) => {
29
- return _mdastSplitter(unwarpRoot(mdast), checker, getSplitterGenerator());
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