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.
- package/README.md +86 -4
- package/dist/adapters/index.js +58 -0
- package/dist/adapters/registry.js +149 -0
- package/dist/adapters/sites/declarative.js +178 -0
- package/dist/adapters/sites/hackernews.js +94 -0
- package/dist/adapters/sites/youtube.js +165 -0
- package/dist/adapters/types.js +0 -0
- package/dist/bin.js +4142 -549
- package/dist/cjs/adapters/index.js +86 -0
- package/dist/cjs/adapters/registry.js +178 -0
- package/dist/cjs/adapters/sites/declarative.js +202 -0
- package/dist/cjs/adapters/sites/hackernews.js +117 -0
- package/dist/cjs/adapters/sites/youtube.js +191 -0
- package/dist/cjs/adapters/types.js +16 -0
- package/dist/cjs/constants.js +2 -2
- package/dist/cjs/detect-client-shell.js +78 -0
- package/dist/cjs/extract-meta-refresh.js +71 -0
- package/dist/cjs/extractors/index.js +5 -0
- package/dist/cjs/extractors/lib/constants.js +186 -0
- package/dist/cjs/extractors/lib/sanitize.js +429 -0
- package/dist/cjs/extractors/lib/score.js +264 -0
- package/dist/cjs/extractors/lib/terminators.js +131 -0
- package/dist/cjs/extractors/lib/ui-chrome.js +119 -0
- package/dist/cjs/extractors/pipeExtractors.js +12 -9
- package/dist/cjs/extractors/presets/auto.js +44 -0
- package/dist/cjs/extractors/presets/minimal-filter.js +27 -48
- package/dist/cjs/extractors/presets/takumi.js +84 -122
- package/dist/cjs/html-to-markdown.js +44 -7
- package/dist/cjs/html-to-mdast.js +10 -1
- package/dist/cjs/index.js +43 -2
- package/dist/cjs/link-replacer.js +19 -13
- package/dist/cjs/loaders/cf-puppeteer.js +14 -7
- package/dist/cjs/loaders/fetch.js +14 -2
- package/dist/cjs/loaders/playwright.js +32 -30
- package/dist/cjs/loaders/puppeteer.js +14 -7
- package/dist/cjs/md-splitter.js +24 -11
- package/dist/cjs/mdast-handlers/custom-a-handler.js +25 -9
- package/dist/cjs/mdast-handlers/custom-code-handler.js +18 -7
- package/dist/cjs/mdast-handlers/custom-div-handler.js +12 -33
- package/dist/cjs/mdast-handlers/custom-img-handler.js +23 -1
- package/dist/cjs/mdast-handlers/custom-table-handler.js +16 -0
- package/dist/cjs/mdast-handlers/definition-list-handler.js +110 -0
- package/dist/cjs/mdast-handlers/math-handler.js +12 -3
- package/dist/cjs/mdast-handlers/sup-sub-handler.js +39 -0
- package/dist/cjs/mdast-to-markdown.js +2 -5
- package/dist/cjs/metadata/index.js +223 -0
- package/dist/cjs/normalize/index.js +177 -0
- package/dist/cjs/platform/client.js +204 -0
- package/dist/cjs/platform/error.js +36 -0
- package/dist/cjs/platform/fetch.js +37 -0
- package/dist/cjs/platform/index.js +44 -0
- package/dist/cjs/platform/types.js +40 -0
- package/dist/cjs/utils/hast-fast.js +284 -0
- package/dist/constants.js +2 -2
- package/dist/detect-client-shell.js +53 -0
- package/dist/extract-meta-refresh.js +47 -0
- package/dist/extractors/index.js +3 -0
- package/dist/extractors/lib/constants.js +150 -0
- package/dist/extractors/lib/sanitize.js +419 -0
- package/dist/extractors/lib/score.js +242 -0
- package/dist/extractors/lib/terminators.js +107 -0
- package/dist/extractors/lib/ui-chrome.js +96 -0
- package/dist/extractors/pipeExtractors.js +12 -9
- package/dist/extractors/presets/auto.js +20 -0
- package/dist/extractors/presets/minimal-filter.js +35 -47
- package/dist/extractors/presets/takumi.js +91 -122
- package/dist/html-to-markdown.js +42 -6
- package/dist/html-to-mdast.js +10 -1
- package/dist/index.js +45 -2
- package/dist/link-replacer.js +19 -13
- package/dist/loaders/cf-puppeteer.js +15 -8
- package/dist/loaders/fetch.js +14 -2
- package/dist/loaders/playwright.js +32 -30
- package/dist/loaders/puppeteer.js +15 -8
- package/dist/md-splitter.js +24 -11
- package/dist/mdast-handlers/custom-a-handler.js +25 -9
- package/dist/mdast-handlers/custom-code-handler.js +17 -7
- package/dist/mdast-handlers/custom-div-handler.js +12 -33
- package/dist/mdast-handlers/custom-img-handler.js +23 -1
- package/dist/mdast-handlers/custom-table-handler.js +16 -0
- package/dist/mdast-handlers/definition-list-handler.js +87 -0
- package/dist/mdast-handlers/math-handler.js +12 -3
- package/dist/mdast-handlers/sup-sub-handler.js +16 -0
- package/dist/mdast-to-markdown.js +2 -5
- package/dist/metadata/index.js +198 -0
- package/dist/normalize/index.js +159 -0
- package/dist/platform/client.js +182 -0
- package/dist/platform/error.js +13 -0
- package/dist/platform/fetch.js +14 -0
- package/dist/platform/index.js +23 -0
- package/dist/platform/types.js +13 -0
- package/dist/types/adapters/index.d.ts +24 -0
- package/dist/types/adapters/index.js.map +1 -0
- package/dist/types/adapters/registry.d.ts +35 -0
- package/dist/types/adapters/registry.js.map +1 -0
- package/dist/types/adapters/sites/declarative.d.ts +13 -0
- package/dist/types/adapters/sites/declarative.js.map +1 -0
- package/dist/types/adapters/sites/hackernews.d.ts +13 -0
- package/dist/types/adapters/sites/hackernews.js.map +1 -0
- package/dist/types/adapters/sites/youtube.d.ts +31 -0
- package/dist/types/adapters/sites/youtube.js.map +1 -0
- package/dist/types/adapters/types.d.ts +99 -0
- package/dist/types/adapters/types.js.map +1 -0
- package/dist/types/cli/bin.js.map +1 -1
- package/dist/types/cli/commands/jobs/files.d.ts +28 -0
- package/dist/types/cli/commands/jobs/files.js.map +1 -0
- package/dist/types/cli/commands/jobs/llms-txt.d.ts +33 -0
- package/dist/types/cli/commands/jobs/llms-txt.js.map +1 -0
- package/dist/types/cli/commands/jobs/options.d.ts +58 -0
- package/dist/types/cli/commands/jobs/options.js.map +1 -0
- package/dist/types/cli/commands/jobs/run.d.ts +44 -0
- package/dist/types/cli/commands/jobs/run.js.map +1 -0
- package/dist/types/cli/commands/skill/content.d.ts +9 -0
- package/dist/types/cli/commands/skill/content.js.map +1 -0
- package/dist/types/cli/commands/skill/index.d.ts +19 -0
- package/dist/types/cli/commands/skill/index.js.map +1 -0
- package/dist/types/cli/commands/webforai/interactive.d.ts +7 -0
- package/dist/types/cli/commands/webforai/interactive.js.map +1 -0
- package/dist/types/cli/commands/webforai/loadHtml.js.map +1 -1
- package/dist/types/cli/commands/webforai/options.d.ts +62 -0
- package/dist/types/cli/commands/webforai/options.js.map +1 -0
- package/dist/types/cli/commands/webforai/run.d.ts +10 -0
- package/dist/types/cli/commands/webforai/run.js.map +1 -0
- package/dist/types/cli/constants.d.ts +14 -4
- package/dist/types/cli/constants.js.map +1 -1
- package/dist/types/cli/helpers/selectExtractMode.js.map +1 -1
- package/dist/types/cli/helpers/selectLoader.js.map +1 -1
- package/dist/types/cli/utils.d.ts +8 -1
- package/dist/types/cli/utils.js.map +1 -1
- package/dist/types/constants.d.ts +7 -1
- package/dist/types/constants.js.map +1 -1
- package/dist/types/detect-client-shell.d.ts +24 -0
- package/dist/types/detect-client-shell.js.map +1 -0
- package/dist/types/extract-meta-refresh.d.ts +29 -0
- package/dist/types/extract-meta-refresh.js.map +1 -0
- package/dist/types/extractors/index.d.ts +1 -0
- package/dist/types/extractors/index.js.map +1 -1
- package/dist/types/extractors/lib/constants.d.ts +81 -0
- package/dist/types/extractors/lib/constants.js.map +1 -0
- package/dist/types/extractors/lib/sanitize.d.ts +66 -0
- package/dist/types/extractors/lib/sanitize.js.map +1 -0
- package/dist/types/extractors/lib/score.d.ts +85 -0
- package/dist/types/extractors/lib/score.js.map +1 -0
- package/dist/types/extractors/lib/terminators.d.ts +26 -0
- package/dist/types/extractors/lib/terminators.js.map +1 -0
- package/dist/types/extractors/lib/ui-chrome.d.ts +18 -0
- package/dist/types/extractors/lib/ui-chrome.js.map +1 -0
- package/dist/types/extractors/pipeExtractors.d.ts +5 -0
- package/dist/types/extractors/pipeExtractors.js.map +1 -1
- package/dist/types/extractors/presets/auto.d.ts +30 -0
- package/dist/types/extractors/presets/auto.js.map +1 -0
- package/dist/types/extractors/presets/minimal-filter.d.ts +10 -2
- package/dist/types/extractors/presets/minimal-filter.js.map +1 -1
- package/dist/types/extractors/presets/takumi.d.ts +19 -3
- package/dist/types/extractors/presets/takumi.js.map +1 -1
- package/dist/types/extractors/types.d.ts +8 -0
- package/dist/types/html-to-markdown.d.ts +38 -0
- package/dist/types/html-to-markdown.js.map +1 -1
- package/dist/types/html-to-mdast.d.ts +11 -0
- package/dist/types/html-to-mdast.js.map +1 -1
- package/dist/types/index.d.ts +8 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/link-replacer.d.ts +3 -1
- package/dist/types/link-replacer.js.map +1 -1
- package/dist/types/loaders/cf-puppeteer.js.map +1 -1
- package/dist/types/loaders/fetch.d.ts +4 -0
- package/dist/types/loaders/fetch.js.map +1 -1
- package/dist/types/loaders/playwright.js.map +1 -1
- package/dist/types/loaders/puppeteer.js.map +1 -1
- package/dist/types/md-splitter.d.ts +1 -1
- package/dist/types/md-splitter.js.map +1 -1
- package/dist/types/mdast-handlers/custom-a-handler.js.map +1 -1
- package/dist/types/mdast-handlers/custom-code-handler.d.ts +2 -0
- package/dist/types/mdast-handlers/custom-code-handler.js.map +1 -1
- package/dist/types/mdast-handlers/custom-div-handler.js.map +1 -1
- package/dist/types/mdast-handlers/custom-img-handler.js.map +1 -1
- package/dist/types/mdast-handlers/custom-table-handler.js.map +1 -1
- package/dist/types/mdast-handlers/definition-list-handler.d.ts +2 -0
- package/dist/types/mdast-handlers/definition-list-handler.js.map +1 -0
- package/dist/types/mdast-handlers/math-handler.js.map +1 -1
- package/dist/types/mdast-handlers/sup-sub-handler.d.ts +10 -0
- package/dist/types/mdast-handlers/sup-sub-handler.js.map +1 -0
- package/dist/types/mdast-to-markdown.js.map +1 -1
- package/dist/types/metadata/index.d.ts +47 -0
- package/dist/types/metadata/index.js.map +1 -0
- package/dist/types/normalize/index.d.ts +53 -0
- package/dist/types/normalize/index.js.map +1 -0
- package/dist/types/platform/client.d.ts +55 -0
- package/dist/types/platform/client.js.map +1 -0
- package/dist/types/platform/error.d.ts +22 -0
- package/dist/types/platform/error.js.map +1 -0
- package/dist/types/platform/fetch.d.ts +36 -0
- package/dist/types/platform/fetch.js.map +1 -0
- package/dist/types/platform/index.d.ts +4 -0
- package/dist/types/platform/index.js.map +1 -0
- package/dist/types/platform/types.d.ts +164 -0
- package/dist/types/platform/types.js.map +1 -0
- package/dist/types/utils/hast-fast.d.ts +101 -0
- package/dist/types/utils/hast-fast.js.map +1 -0
- package/dist/utils/hast-fast.js +246 -0
- package/package.json +25 -7
- package/dist/types/cli/commands/webforai/index.d.ts +0 -6
- 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
|
|
6
|
-
|
|
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
|
-
|
|
10
|
+
continue;
|
|
9
11
|
}
|
|
10
|
-
if (typeof extractor
|
|
11
|
-
|
|
12
|
+
if (typeof extractor !== "function") {
|
|
13
|
+
throw new Error(`Invalid extractor: ${String(extractor)}`);
|
|
12
14
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
};
|
|
14
|
-
var
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
22
|
-
if (
|
|
23
|
-
return
|
|
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 (
|
|
35
|
-
return
|
|
24
|
+
if (role === "dialog" && isTruthyAttribute(element, "aria-modal")) {
|
|
25
|
+
return true;
|
|
36
26
|
}
|
|
37
|
-
if (element
|
|
38
|
-
return
|
|
27
|
+
if (stringProperty(element, "rel") === "author") {
|
|
28
|
+
return true;
|
|
39
29
|
}
|
|
40
30
|
if (REGEXPS.byline.test(matchString(element))) {
|
|
41
|
-
return
|
|
42
|
-
}
|
|
43
|
-
if (element.properties.role && UNLIKELY_ROLES.includes(element.properties.role)) {
|
|
44
|
-
return false;
|
|
31
|
+
return true;
|
|
45
32
|
}
|
|
46
|
-
return
|
|
33
|
+
return false;
|
|
47
34
|
};
|
|
48
35
|
var minimalFilter = (params) => {
|
|
49
36
|
const { hast } = params;
|
|
50
|
-
const body =
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
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
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
return
|
|
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
|