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
@@ -0,0 +1,242 @@
1
+ // src/extractors/lib/score.ts
2
+ import {
3
+ classList,
4
+ isElement,
5
+ matchString,
6
+ numericProperty,
7
+ stringProperty,
8
+ walk
9
+ } from "../../utils/hast-fast.js";
10
+ import { CANDIDATE_TAGS, CONTENT_ROLES, REGEXPS, SCOREABLE_TAGS, minContentLength } from "./constants.js";
11
+ var TAG_BASE_SCORE = {
12
+ div: 5,
13
+ pre: 3,
14
+ td: 3,
15
+ blockquote: 3,
16
+ article: 10,
17
+ main: 10,
18
+ section: 5,
19
+ address: -3,
20
+ ol: -3,
21
+ ul: -3,
22
+ dl: -3,
23
+ dd: -3,
24
+ dt: -3,
25
+ li: -3,
26
+ form: -3,
27
+ h1: -5,
28
+ h2: -5,
29
+ h3: -5,
30
+ h4: -5,
31
+ h5: -5,
32
+ h6: -5,
33
+ th: -5
34
+ };
35
+ var MAX_ANCESTOR_DEPTH = 5;
36
+ var MIN_SCOREABLE_TEXT = 25;
37
+ var buildScoringContext = (root, collector, lang) => {
38
+ const parents = /* @__PURE__ */ new WeakMap();
39
+ const scoreable = [];
40
+ let hasGeometry = false;
41
+ walk(root, (node, parent) => {
42
+ if (parent) {
43
+ parents.set(node, parent);
44
+ }
45
+ if (!isElement(node)) {
46
+ return;
47
+ }
48
+ if (!hasGeometry && numericProperty(node, "data-rheight") !== void 0) {
49
+ hasGeometry = true;
50
+ }
51
+ if (isParagraphLike(node)) {
52
+ scoreable.push(node);
53
+ }
54
+ });
55
+ return { collector, parents, lang, hasGeometry, scoreable };
56
+ };
57
+ var classWeight = (element) => {
58
+ let weight = 0;
59
+ const id = stringProperty(element, "id") ?? "";
60
+ const classes = classList(element).join(" ");
61
+ if (REGEXPS.negative.test(classes)) {
62
+ weight -= 25;
63
+ }
64
+ if (REGEXPS.positive.test(classes)) {
65
+ weight += 25;
66
+ }
67
+ if (REGEXPS.negative.test(id)) {
68
+ weight -= 25;
69
+ }
70
+ if (REGEXPS.positive.test(id)) {
71
+ weight += 25;
72
+ }
73
+ return weight;
74
+ };
75
+ var semanticBonus = (element) => {
76
+ let bonus = 0;
77
+ if (element.tagName === "article") {
78
+ bonus += 60;
79
+ }
80
+ if (element.tagName === "main") {
81
+ bonus += 50;
82
+ }
83
+ const role = stringProperty(element, "role");
84
+ if (role && CONTENT_ROLES.has(role)) {
85
+ bonus += 40;
86
+ }
87
+ const itemprop = stringProperty(element, "itemprop");
88
+ if (itemprop?.includes("articleBody")) {
89
+ bonus += 80;
90
+ }
91
+ const match = matchString(element);
92
+ if (/(^|[\s_-])(markdown|prose|article-?content|post-?content|entry-?content|doc-?content|md-?content)([\s_-]|$)/i.test(
93
+ match
94
+ )) {
95
+ bonus += 35;
96
+ }
97
+ return bonus;
98
+ };
99
+ var geometryMultiplier = (element, context) => {
100
+ if (!context.hasGeometry) {
101
+ return 1;
102
+ }
103
+ const height = numericProperty(element, "data-rheight");
104
+ const width = numericProperty(element, "data-rwidth");
105
+ if (height === void 0 || width === void 0) {
106
+ return 1;
107
+ }
108
+ if (height === 0 || width === 0) {
109
+ return 0;
110
+ }
111
+ if (width < 220) {
112
+ return 0.35;
113
+ }
114
+ return 1;
115
+ };
116
+ var paragraphScore = (textLength, commas, densityDivisor) => 1 + commas + Math.min(Math.floor(textLength / densityDivisor), 3);
117
+ var ancestorWeight = (level) => {
118
+ if (level === 0) {
119
+ return 1;
120
+ }
121
+ if (level === 1) {
122
+ return 2;
123
+ }
124
+ return level * 3;
125
+ };
126
+ var isParagraphLike = (element) => {
127
+ if (!SCOREABLE_TAGS.has(element.tagName)) {
128
+ return false;
129
+ }
130
+ if (element.tagName !== "div" && element.tagName !== "section") {
131
+ return true;
132
+ }
133
+ return !element.children.some((child) => isElement(child) && CANDIDATE_TAGS.has(child.tagName));
134
+ };
135
+ var scoreCandidates = (_root, context) => {
136
+ const { collector, parents } = context;
137
+ const scores = /* @__PURE__ */ new Map();
138
+ const densityDivisor = minContentLength(context.lang) / 4;
139
+ for (const node of context.scoreable) {
140
+ const metrics = collector.metrics(node);
141
+ if (metrics.text < MIN_SCOREABLE_TEXT) {
142
+ continue;
143
+ }
144
+ const contribution = paragraphScore(metrics.text, metrics.commas, densityDivisor);
145
+ let current = node;
146
+ for (let level = 0; level < MAX_ANCESTOR_DEPTH && current; level++) {
147
+ const parent = parents.get(current);
148
+ if (!(parent && isElement(parent))) {
149
+ break;
150
+ }
151
+ if (CANDIDATE_TAGS.has(parent.tagName) || parent.tagName === "article" || parent.tagName === "main") {
152
+ const base = scores.get(parent) ?? (TAG_BASE_SCORE[parent.tagName] ?? 0) + classWeight(parent) + semanticBonus(parent);
153
+ scores.set(parent, base + contribution / ancestorWeight(level));
154
+ }
155
+ current = parent;
156
+ }
157
+ }
158
+ const candidates = [];
159
+ for (const [element, rawScore] of scores) {
160
+ const density = collector.linkDensity(element);
161
+ const score = rawScore * (1 - density) * geometryMultiplier(element, context);
162
+ if (score > 0) {
163
+ candidates.push({ element, score });
164
+ }
165
+ }
166
+ candidates.sort((a, b) => b.score - a.score);
167
+ return candidates;
168
+ };
169
+ var collectArticleNodes = (top, candidates, context) => {
170
+ const { collector, parents } = context;
171
+ const parent = parents.get(top.element);
172
+ if (!(parent && isElement(parent))) {
173
+ return [top.element];
174
+ }
175
+ const scoreOf = new Map(candidates.map((candidate) => [candidate.element, candidate.score]));
176
+ const threshold = Math.max(10, top.score * 0.2);
177
+ const selected = [];
178
+ for (const child of parent.children) {
179
+ if (!isElement(child)) {
180
+ continue;
181
+ }
182
+ if (child === top.element) {
183
+ selected.push(child);
184
+ continue;
185
+ }
186
+ const siblingScore = scoreOf.get(child) ?? 0;
187
+ if (siblingScore >= threshold) {
188
+ selected.push(child);
189
+ continue;
190
+ }
191
+ if (child.tagName === "p" || child.tagName === "section") {
192
+ const metrics = collector.metrics(child);
193
+ const density = collector.linkDensity(child);
194
+ if (metrics.text > 80 && density < 0.25) {
195
+ selected.push(child);
196
+ }
197
+ }
198
+ }
199
+ return selected.length > 0 ? selected : [top.element];
200
+ };
201
+ var MAX_CLIMB_HOPS = 5;
202
+ var MIN_CLIMB_RELATIVE_GAIN = 0.08;
203
+ var MIN_CLIMB_ABSOLUTE_GAIN = 500;
204
+ var MAX_CLIMB_GAIN_LINK_DENSITY = 1 / 3;
205
+ var CLIMB_SCORE_FLOOR = 1 / 3;
206
+ var climbToArticleBoundary = (top, candidates, context) => {
207
+ const { collector, parents } = context;
208
+ const scoreOf = new Map(candidates.map((candidate) => [candidate.element, candidate.score]));
209
+ const scoreFloor = top.score * CLIMB_SCORE_FLOOR;
210
+ let current = top.element;
211
+ for (let hop = 0; hop < MAX_CLIMB_HOPS; hop++) {
212
+ const parent = parents.get(current);
213
+ if (!(parent && isElement(parent))) {
214
+ break;
215
+ }
216
+ const parentScore = scoreOf.get(parent);
217
+ if (parentScore === void 0 || parentScore < scoreFloor) {
218
+ break;
219
+ }
220
+ const currentMetrics = collector.metrics(current);
221
+ const parentMetrics = collector.metrics(parent);
222
+ const gainedText = parentMetrics.text - currentMetrics.text;
223
+ if (gainedText < Math.max(MIN_CLIMB_ABSOLUTE_GAIN, currentMetrics.text * MIN_CLIMB_RELATIVE_GAIN)) {
224
+ break;
225
+ }
226
+ const gainedLink = parentMetrics.link - currentMetrics.link;
227
+ if (gainedLink / gainedText > MAX_CLIMB_GAIN_LINK_DENSITY) {
228
+ break;
229
+ }
230
+ current = parent;
231
+ }
232
+ return current;
233
+ };
234
+ export {
235
+ buildScoringContext,
236
+ classWeight,
237
+ climbToArticleBoundary,
238
+ collectArticleNodes,
239
+ geometryMultiplier,
240
+ scoreCandidates,
241
+ semanticBonus
242
+ };
@@ -0,0 +1,107 @@
1
+ // src/extractors/lib/terminators.ts
2
+ import { isElement, isParent, pruneInPlace } from "../../utils/hast-fast.js";
3
+ var TERMINATOR_PATTERNS = [
4
+ /^was this (page|article|document) helpful/i,
5
+ /^thanks for the feedback/i,
6
+ /^(this page was )?last (modified|updated) (on|at|:)?\s/i,
7
+ /^learn how to contribute/i,
8
+ /^help (us )?improve\b/i,
9
+ // The stranded Yes/No pair of a feedback widget whose question was removed separately.
10
+ /^yes ?no$/i,
11
+ /^(related (articles?|posts?|stories|content)|you might also like|recommended for you|more from|read next)$/i,
12
+ /^(comments?|leave a (comment|reply)|join the (conversation|discussion))$/i,
13
+ /^(この(ページ|記事)は(参考|役に立ち)ました?か)/,
14
+ /^(関連記事|あわせて読みたい|こちらもおすすめ|おすすめ記事)$/,
15
+ /^(コメントを(残す|書く)|コメント一覧)$/,
16
+ /^最終更新日?[::\s]/
17
+ ];
18
+ var TAIL_START_FRACTION = 0.8;
19
+ var MAX_TERMINATOR_BLOCK_LENGTH = 500;
20
+ var BLOCK_TAGS = /* @__PURE__ */ new Set(["p", "div", "section", "footer", "aside", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol"]);
21
+ var isTerminatorBlock = (element, start, tailStart) => {
22
+ if (!BLOCK_TAGS.has(element.tagName) || start < tailStart) {
23
+ return false;
24
+ }
25
+ const text = textOf(element).replace(/\s+/g, " ").trim();
26
+ if (text.length === 0 || text.length > MAX_TERMINATOR_BLOCK_LENGTH) {
27
+ return false;
28
+ }
29
+ return TERMINATOR_PATTERNS.some((pattern) => pattern.test(text));
30
+ };
31
+ var textOf = (node) => {
32
+ if (node.type === "text") {
33
+ return node.value;
34
+ }
35
+ if (!isParent(node)) {
36
+ return "";
37
+ }
38
+ let text = "";
39
+ for (const child of node.children) {
40
+ text += textOf(child);
41
+ }
42
+ return text;
43
+ };
44
+ var truncateTrailingBoilerplate = (tree) => {
45
+ const starts = /* @__PURE__ */ new Map();
46
+ let total = 0;
47
+ const measure = (node) => {
48
+ starts.set(node, total);
49
+ if (node.type === "text") {
50
+ total += node.value.trim().length;
51
+ return;
52
+ }
53
+ if (isParent(node)) {
54
+ for (const child of node.children) {
55
+ measure(child);
56
+ }
57
+ }
58
+ };
59
+ measure(tree);
60
+ if (total === 0) {
61
+ return false;
62
+ }
63
+ const tailStart = total * TAIL_START_FRACTION;
64
+ let cutoff = -1;
65
+ const find = (node) => {
66
+ if (cutoff !== -1 || !isParent(node)) {
67
+ return;
68
+ }
69
+ for (const child of node.children) {
70
+ if (cutoff !== -1) {
71
+ return;
72
+ }
73
+ if (!isElement(child)) {
74
+ continue;
75
+ }
76
+ const start = starts.get(child) ?? 0;
77
+ if (isTerminatorBlock(child, start, tailStart)) {
78
+ cutoff = start;
79
+ return;
80
+ }
81
+ find(child);
82
+ }
83
+ };
84
+ find(tree);
85
+ if (cutoff === -1) {
86
+ return false;
87
+ }
88
+ pruneInPlace(tree, (node) => {
89
+ if (node === tree) {
90
+ return true;
91
+ }
92
+ const start = starts.get(node);
93
+ if (start === void 0) {
94
+ return true;
95
+ }
96
+ if (start >= cutoff) {
97
+ return false;
98
+ }
99
+ return true;
100
+ });
101
+ return true;
102
+ };
103
+ var TERMINATOR_TAIL_FRACTION = TAIL_START_FRACTION;
104
+ export {
105
+ TERMINATOR_TAIL_FRACTION,
106
+ truncateTrailingBoilerplate
107
+ };
@@ -0,0 +1,96 @@
1
+ // src/extractors/lib/ui-chrome.ts
2
+ var UI_PHRASES = /* @__PURE__ */ new Set([
3
+ // Documentation and article controls
4
+ "copy",
5
+ "copy page",
6
+ "copy link",
7
+ "copy code",
8
+ "copied",
9
+ "edit this page",
10
+ "edit on github",
11
+ "on this page",
12
+ "in this article",
13
+ "table of contents",
14
+ "contents",
15
+ "print",
16
+ "print this page",
17
+ "back to top",
18
+ "scroll to top",
19
+ "skip to content",
20
+ "skip to main content",
21
+ "permalink",
22
+ "anchor",
23
+ // Feedback widgets
24
+ "was this page helpful",
25
+ "was this helpful",
26
+ "was this article helpful",
27
+ "yes no",
28
+ "feedback",
29
+ "send feedback",
30
+ "report an issue",
31
+ "rate this page",
32
+ // Sharing and subscription
33
+ "share",
34
+ "share this",
35
+ "share this article",
36
+ "share on twitter",
37
+ "share on facebook",
38
+ "subscribe",
39
+ "sign up",
40
+ "sign in",
41
+ "log in",
42
+ "login",
43
+ "newsletter",
44
+ // Advertising and promotion
45
+ "advertisement",
46
+ "advertisements",
47
+ "sponsored",
48
+ "sponsored content",
49
+ "promoted",
50
+ // Navigation affordances
51
+ "read more",
52
+ "show more",
53
+ "show less",
54
+ "see more",
55
+ "load more",
56
+ "previous",
57
+ "next",
58
+ "previous page",
59
+ "next page",
60
+ "menu",
61
+ "close",
62
+ "search",
63
+ // Japanese equivalents
64
+ "\u76EE\u6B21",
65
+ "\u5E83\u544A",
66
+ "\u30B7\u30A7\u30A2",
67
+ "\u5171\u6709",
68
+ "\u3053\u306E\u8A18\u4E8B\u3092\u30B7\u30A7\u30A2",
69
+ "\u7D9A\u304D\u3092\u8AAD\u3080",
70
+ "\u3082\u3063\u3068\u898B\u308B",
71
+ "\u95A2\u9023\u8A18\u4E8B",
72
+ "\u30B9\u30DD\u30F3\u30B5\u30FC\u30EA\u30F3\u30AF",
73
+ "\u30DA\u30FC\u30B8\u306E\u5148\u982D\u3078",
74
+ "\u30C8\u30C3\u30D7\u3078\u623B\u308B",
75
+ "\u524D\u3078",
76
+ "\u6B21\u3078",
77
+ "\u9589\u3058\u308B",
78
+ "\u691C\u7D22",
79
+ "\u30ED\u30B0\u30A4\u30F3",
80
+ "\u4F1A\u54E1\u767B\u9332"
81
+ ]);
82
+ var MAX_PHRASE_LENGTH = 40;
83
+ var normalize = (text) => text.toLowerCase().replace(/[?!.,:;·・…"'“”‘’()[\]{}<>|/\\—–-]/g, " ").replace(/\s+/g, " ").trim();
84
+ var isUiChromeText = (text) => {
85
+ if (text.length === 0 || text.length > MAX_PHRASE_LENGTH) {
86
+ return false;
87
+ }
88
+ const normalized = normalize(text);
89
+ if (normalized.length === 0) {
90
+ return false;
91
+ }
92
+ return UI_PHRASES.has(normalized);
93
+ };
94
+ export {
95
+ isUiChromeText
96
+ };
@@ -1,18 +1,21 @@
1
1
  // src/extractors/pipeExtractors.ts
2
2
  import { DEFAULT_EXTRACTORS } from "../constants.js";
3
3
  var pipeExtractors = (params, extractors = DEFAULT_EXTRACTORS) => {
4
- const { hast, lang } = params;
5
- const _extractors = Array.isArray(extractors) ? extractors : [extractors];
6
- const extracted = _extractors.reduce((acc, extractor) => {
4
+ const { hast, lang, url, owned } = params;
5
+ const list = Array.isArray(extractors) ? extractors : [extractors];
6
+ let current = hast;
7
+ let currentOwned = owned ?? false;
8
+ for (const extractor of list) {
7
9
  if (extractor === false) {
8
- return acc;
10
+ continue;
9
11
  }
10
- if (typeof extractor === "function") {
11
- return extractor({ hast: acc, lang });
12
+ if (typeof extractor !== "function") {
13
+ throw new Error(`Invalid extractor: ${String(extractor)}`);
12
14
  }
13
- throw new Error(`Invalid extractor: ${extractor}`);
14
- }, hast) || hast;
15
- return extracted;
15
+ current = extractor({ hast: current, lang, url, owned: currentOwned });
16
+ currentOwned = true;
17
+ }
18
+ return current;
16
19
  };
17
20
  export {
18
21
  pipeExtractors
@@ -0,0 +1,20 @@
1
+ // src/extractors/presets/auto.ts
2
+ import { runAdapters } from "../../adapters/index.js";
3
+ import { takumiExtractor } from "./takumi.js";
4
+ var createAutoExtractor = (options = {}) => {
5
+ const { adapters } = options;
6
+ return (params) => {
7
+ if (adapters !== false) {
8
+ const extracted = runAdapters({ hast: params.hast, url: params.url, lang: params.lang }, adapters ?? void 0);
9
+ if (extracted) {
10
+ return extracted;
11
+ }
12
+ }
13
+ return takumiExtractor(params);
14
+ };
15
+ };
16
+ var autoExtractor = createAutoExtractor();
17
+ export {
18
+ autoExtractor,
19
+ createAutoExtractor
20
+ };
@@ -1,63 +1,51 @@
1
1
  // src/extractors/presets/minimal-filter.ts
2
- import { select } 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 metadataFilter = (node) => {
15
- return !(["comment", "doctype"].includes(node.type) || node.type === "element" && ["script", "style", "link", "meta", "noscript", "svg", "title"].includes(node.tagName));
16
- };
17
- var universalElementFilter = (node) => {
18
- if (node.type !== "element") {
2
+ import {
3
+ MetricsCollector,
4
+ cloneHast,
5
+ findElement,
6
+ isElement,
7
+ isTruthyAttribute,
8
+ matchString,
9
+ pruneInPlace,
10
+ stringProperty
11
+ } from "../../utils/hast-fast.js";
12
+ import { REGEXPS, UNLIKELY_ROLES } from "../lib/constants.js";
13
+ import { stripNonContent } from "../lib/sanitize.js";
14
+ var MIN_RETAINED_FRACTION = 1 / 3;
15
+ var ALWAYS_ACCEPT_LENGTH = 5e3;
16
+ var isChrome = (element) => {
17
+ if (element.tagName === "aside" || element.tagName === "nav" || element.tagName === "dialog") {
19
18
  return true;
20
19
  }
21
- const element = node;
22
- if (["aside", "nav"].includes(element.tagName)) {
23
- return false;
24
- }
25
- if (["hidden", "aria-hidden"].some((key) => element.properties[key])) {
26
- return false;
27
- }
28
- if (classnames(element).some((classname) => REGEXPS.hidden.test(classname))) {
29
- return false;
30
- }
31
- if (element.tagName === "dialog") {
32
- return false;
20
+ const role = stringProperty(element, "role");
21
+ if (role && UNLIKELY_ROLES.has(role)) {
22
+ return true;
33
23
  }
34
- if (element.properties.role === "dialog" && element.properties["aria-modal"]) {
35
- return false;
24
+ if (role === "dialog" && isTruthyAttribute(element, "aria-modal")) {
25
+ return true;
36
26
  }
37
- if (element.properties.rel === "author" && isStrInclude(element.properties.itemprop, "author")) {
38
- return false;
27
+ if (stringProperty(element, "rel") === "author") {
28
+ return true;
39
29
  }
40
30
  if (REGEXPS.byline.test(matchString(element))) {
41
- return false;
42
- }
43
- if (element.properties.role && UNLIKELY_ROLES.includes(element.properties.role)) {
44
- return false;
31
+ return true;
45
32
  }
46
- return true;
33
+ return false;
47
34
  };
48
35
  var minimalFilter = (params) => {
49
36
  const { hast } = params;
50
- const body = select("body", hast) ?? hast;
51
- const metadataFilteredHast = filter(body, (node) => metadataFilter(node));
52
- const metadataFilteredHastText = metadataFilteredHast && hastToString(metadataFilteredHast);
53
- if (!(metadataFilteredHast && metadataFilteredHastText)) {
37
+ const body = cloneHast(findElement(hast, (element) => element.tagName === "body") ?? hast);
38
+ stripNonContent(body);
39
+ const collector = new MetricsCollector();
40
+ const baseText = collector.textLength(body);
41
+ if (baseText === 0) {
54
42
  return body;
55
43
  }
56
- const baseFilterd = filter(metadataFilteredHast, (node) => universalElementFilter(node));
57
- const baseFilterdText = baseFilterd ? hastToString(baseFilterd) : "";
58
- const isOverExtracted = baseFilterdText.length > metadataFilteredHastText.length / 3 || baseFilterdText.length > 5e3;
59
- const baseTree = isOverExtracted && baseFilterd ? baseFilterd : metadataFilteredHast;
60
- return baseTree;
44
+ const filtered = cloneHast(body);
45
+ pruneInPlace(filtered, (node) => !(isElement(node) && isChrome(node)));
46
+ const filteredText = new MetricsCollector().textLength(filtered);
47
+ const isOverExtracted = filteredText < baseText * MIN_RETAINED_FRACTION && filteredText < ALWAYS_ACCEPT_LENGTH;
48
+ return isOverExtracted ? body : filtered;
61
49
  };
62
50
  export {
63
51
  minimalFilter