@agentsean/crawler 1.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/LICENSE +661 -0
- package/dist/adaptive.d.ts +9 -0
- package/dist/adaptive.d.ts.map +1 -0
- package/dist/adaptive.js +43 -0
- package/dist/adaptive.js.map +1 -0
- package/dist/crawl.d.ts +4 -0
- package/dist/crawl.d.ts.map +1 -0
- package/dist/crawl.js +348 -0
- package/dist/crawl.js.map +1 -0
- package/dist/decode.d.ts +7 -0
- package/dist/decode.d.ts.map +1 -0
- package/dist/decode.js +53 -0
- package/dist/decode.js.map +1 -0
- package/dist/extract.d.ts +4 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +268 -0
- package/dist/extract.js.map +1 -0
- package/dist/hash.d.ts +4 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +11 -0
- package/dist/hash.js.map +1 -0
- package/dist/http.d.ts +38 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +207 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/persist.d.ts +20 -0
- package/dist/persist.d.ts.map +1 -0
- package/dist/persist.js +186 -0
- package/dist/persist.js.map +1 -0
- package/dist/probe.d.ts +4 -0
- package/dist/probe.d.ts.map +1 -0
- package/dist/probe.js +132 -0
- package/dist/probe.js.map +1 -0
- package/dist/readability.d.ts +9 -0
- package/dist/readability.d.ts.map +1 -0
- package/dist/readability.js +60 -0
- package/dist/readability.js.map +1 -0
- package/dist/render.d.ts +15 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +87 -0
- package/dist/render.js.map +1 -0
- package/dist/robots.d.ts +13 -0
- package/dist/robots.d.ts.map +1 -0
- package/dist/robots.js +187 -0
- package/dist/robots.js.map +1 -0
- package/dist/simhash.d.ts +9 -0
- package/dist/simhash.d.ts.map +1 -0
- package/dist/simhash.js +58 -0
- package/dist/simhash.js.map +1 -0
- package/dist/sitemap.d.ts +3 -0
- package/dist/sitemap.d.ts.map +1 -0
- package/dist/sitemap.js +165 -0
- package/dist/sitemap.js.map +1 -0
- package/dist/types.d.ts +251 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ua.d.ts +3 -0
- package/dist/ua.d.ts.map +1 -0
- package/dist/ua.js +3 -0
- package/dist/ua.js.map +1 -0
- package/dist/url.d.ts +11 -0
- package/dist/url.d.ts.map +1 -0
- package/dist/url.js +114 -0
- package/dist/url.js.map +1 -0
- package/package.json +60 -0
package/dist/persist.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { crawls, entitySightings, findings, pages, pageSnapshots, sites, } from "@agentsean/db";
|
|
3
|
+
import { eq } from "drizzle-orm";
|
|
4
|
+
import { urlHash } from "./hash.js";
|
|
5
|
+
export async function persistCrawl(db, result, siteName, opts) {
|
|
6
|
+
const now = new Date().toISOString();
|
|
7
|
+
const existing = db.select().from(sites).where(eq(sites.origin, result.origin)).all();
|
|
8
|
+
let siteId = existing[0]?.id;
|
|
9
|
+
if (!siteId) {
|
|
10
|
+
siteId = randomUUID();
|
|
11
|
+
const observeUntil = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
12
|
+
db.insert(sites)
|
|
13
|
+
.values({
|
|
14
|
+
id: siteId,
|
|
15
|
+
origin: result.origin,
|
|
16
|
+
name: siteName ?? result.origin,
|
|
17
|
+
observeUntil,
|
|
18
|
+
createdAt: now,
|
|
19
|
+
updatedAt: now,
|
|
20
|
+
})
|
|
21
|
+
.run();
|
|
22
|
+
}
|
|
23
|
+
recordCrawlEntity(db, siteId, result.origin + "/", now);
|
|
24
|
+
const status = opts?.status ??
|
|
25
|
+
(result.aborted ? "running" : result.truncated ? "running" : "complete");
|
|
26
|
+
let crawlId = opts?.crawlId;
|
|
27
|
+
if (crawlId) {
|
|
28
|
+
const row = db.select().from(crawls).where(eq(crawls.id, crawlId)).get();
|
|
29
|
+
if (row) {
|
|
30
|
+
db.update(crawls)
|
|
31
|
+
.set({
|
|
32
|
+
finishedAt: status === "complete" ? result.finishedAt : null,
|
|
33
|
+
status,
|
|
34
|
+
pagesSeen: (row.pagesSeen ?? 0) + result.pagesSeen,
|
|
35
|
+
pagesChanged: (row.pagesChanged ?? 0) + result.pagesChanged,
|
|
36
|
+
error: result.aborted ? "aborted" : null,
|
|
37
|
+
})
|
|
38
|
+
.where(eq(crawls.id, crawlId))
|
|
39
|
+
.run();
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
crawlId = undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (!crawlId) {
|
|
46
|
+
crawlId = randomUUID();
|
|
47
|
+
db.insert(crawls)
|
|
48
|
+
.values({
|
|
49
|
+
id: crawlId,
|
|
50
|
+
siteId,
|
|
51
|
+
startedAt: result.startedAt,
|
|
52
|
+
finishedAt: status === "complete" ? result.finishedAt : null,
|
|
53
|
+
status,
|
|
54
|
+
pagesSeen: result.pagesSeen,
|
|
55
|
+
pagesChanged: result.pagesChanged,
|
|
56
|
+
error: result.aborted ? "aborted" : null,
|
|
57
|
+
})
|
|
58
|
+
.run();
|
|
59
|
+
}
|
|
60
|
+
for (const page of result.pages) {
|
|
61
|
+
persistPage(db, siteId, crawlId, page, now);
|
|
62
|
+
}
|
|
63
|
+
return { siteId, crawlId };
|
|
64
|
+
}
|
|
65
|
+
function persistPage(db, siteId, crawlId, page, now) {
|
|
66
|
+
const hash = urlHash(page.url);
|
|
67
|
+
const found = db
|
|
68
|
+
.select()
|
|
69
|
+
.from(pages)
|
|
70
|
+
.where(eq(pages.siteId, siteId))
|
|
71
|
+
.all()
|
|
72
|
+
.find((row) => row.urlHash === hash);
|
|
73
|
+
const extract = page.extract;
|
|
74
|
+
const values = {
|
|
75
|
+
url: page.url,
|
|
76
|
+
urlHash: hash,
|
|
77
|
+
statusCode: page.statusCode,
|
|
78
|
+
contentHash: page.contentHash,
|
|
79
|
+
title: extract?.title ?? null,
|
|
80
|
+
metaDescription: extract?.metaDescription ?? null,
|
|
81
|
+
canonical: extract?.canonicalHtml[0] ?? null,
|
|
82
|
+
h1: extract?.h1[0] ?? null,
|
|
83
|
+
wordCount: extract?.mainWordCount ?? extract?.wordCount ?? null,
|
|
84
|
+
lang: extract?.lang ?? null,
|
|
85
|
+
jsonld: extract?.jsonLd.length
|
|
86
|
+
? JSON.stringify(extract.jsonLd.map((j) => j.parsed))
|
|
87
|
+
: null,
|
|
88
|
+
lastCrawledAt: now,
|
|
89
|
+
lastChangedAt: page.notModified ? (found?.lastChangedAt ?? now) : now,
|
|
90
|
+
inlinkCount: page.inlinkCount,
|
|
91
|
+
outlinkCount: page.outlinkCount,
|
|
92
|
+
};
|
|
93
|
+
let pageId;
|
|
94
|
+
if (found) {
|
|
95
|
+
pageId = found.id;
|
|
96
|
+
db.update(pages).set(values).where(eq(pages.id, pageId)).run();
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
pageId = randomUUID();
|
|
100
|
+
db.insert(pages)
|
|
101
|
+
.values({
|
|
102
|
+
id: pageId,
|
|
103
|
+
siteId,
|
|
104
|
+
firstSeenAt: now,
|
|
105
|
+
lastAuditedAt: null,
|
|
106
|
+
...values,
|
|
107
|
+
})
|
|
108
|
+
.run();
|
|
109
|
+
}
|
|
110
|
+
db.insert(pageSnapshots)
|
|
111
|
+
.values({
|
|
112
|
+
id: randomUUID(),
|
|
113
|
+
pageId,
|
|
114
|
+
crawlId,
|
|
115
|
+
fetchedAt: page.fetchedAt,
|
|
116
|
+
statusCode: page.statusCode,
|
|
117
|
+
contentHash: page.contentHash,
|
|
118
|
+
body: page.html ? page.html.slice(0, 2_000_000) : null,
|
|
119
|
+
headers: JSON.stringify(page.headers),
|
|
120
|
+
})
|
|
121
|
+
.run();
|
|
122
|
+
recordCrawlEntity(db, siteId, page.url, now);
|
|
123
|
+
}
|
|
124
|
+
function recordCrawlEntity(db, siteId, url, now) {
|
|
125
|
+
const existing = db
|
|
126
|
+
.select()
|
|
127
|
+
.from(entitySightings)
|
|
128
|
+
.where(eq(entitySightings.siteId, siteId))
|
|
129
|
+
.all()
|
|
130
|
+
.find((row) => row.entity === url);
|
|
131
|
+
if (existing)
|
|
132
|
+
return;
|
|
133
|
+
db.insert(entitySightings)
|
|
134
|
+
.values({
|
|
135
|
+
id: randomUUID(),
|
|
136
|
+
siteId,
|
|
137
|
+
entity: url,
|
|
138
|
+
entityKind: "url",
|
|
139
|
+
source: "crawl",
|
|
140
|
+
firstSeenAt: now,
|
|
141
|
+
})
|
|
142
|
+
.run();
|
|
143
|
+
}
|
|
144
|
+
export function persistFindings(db, siteId, rows) {
|
|
145
|
+
const now = new Date().toISOString();
|
|
146
|
+
const pageRows = db.select().from(pages).where(eq(pages.siteId, siteId)).all();
|
|
147
|
+
const byUrl = new Map(pageRows.map((p) => [p.url, p.id]));
|
|
148
|
+
for (const row of rows) {
|
|
149
|
+
const existing = db
|
|
150
|
+
.select()
|
|
151
|
+
.from(findings)
|
|
152
|
+
.where(eq(findings.fingerprint, row.fingerprint))
|
|
153
|
+
.all()[0];
|
|
154
|
+
if (existing) {
|
|
155
|
+
db.update(findings)
|
|
156
|
+
.set({
|
|
157
|
+
severity: row.severity,
|
|
158
|
+
title: row.title,
|
|
159
|
+
explanation: row.explanation,
|
|
160
|
+
evidence: JSON.stringify(row.evidence),
|
|
161
|
+
status: "open",
|
|
162
|
+
})
|
|
163
|
+
.where(eq(findings.id, existing.id))
|
|
164
|
+
.run();
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
db.insert(findings)
|
|
168
|
+
.values({
|
|
169
|
+
id: randomUUID(),
|
|
170
|
+
siteId,
|
|
171
|
+
pageId: row.pageUrl ? (byUrl.get(row.pageUrl) ?? null) : null,
|
|
172
|
+
ruleId: row.ruleId,
|
|
173
|
+
severity: row.severity,
|
|
174
|
+
autonomyTier: row.autonomyTier,
|
|
175
|
+
title: row.title,
|
|
176
|
+
explanation: row.explanation,
|
|
177
|
+
evidence: JSON.stringify(row.evidence),
|
|
178
|
+
status: "open",
|
|
179
|
+
fingerprint: row.fingerprint,
|
|
180
|
+
firstDetectedAt: now,
|
|
181
|
+
resolvedAt: null,
|
|
182
|
+
})
|
|
183
|
+
.run();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=persist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persist.js","sourceRoot":"","sources":["../src/persist.ts"],"names":[],"mappings":"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"}
|
package/dist/probe.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../src/probe.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAKpC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,KAAK,GACX,OAAO,CAAC,WAAW,CAAC,CA2FtB"}
|
package/dist/probe.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import tls from "node:tls";
|
|
2
|
+
import { fetchUrl } from "./http.js";
|
|
3
|
+
import { contentHash } from "./hash.js";
|
|
4
|
+
import { originOf } from "./url.js";
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
export async function probeOrigin(startUrl, agent) {
|
|
7
|
+
const origin = originOf(startUrl);
|
|
8
|
+
const u = new URL(origin);
|
|
9
|
+
const https = u.protocol === "https:";
|
|
10
|
+
let httpRedirectsToHttps = null;
|
|
11
|
+
let wwwSplit = null;
|
|
12
|
+
let wwwPreferred = null;
|
|
13
|
+
let trailingSlashSplit = null;
|
|
14
|
+
let randomSoft404 = false;
|
|
15
|
+
let randomSoft404Url = null;
|
|
16
|
+
let randomSoft404Hash = null;
|
|
17
|
+
let hsts = null;
|
|
18
|
+
if (https) {
|
|
19
|
+
try {
|
|
20
|
+
const httpUrl = `http://${u.host}${u.pathname}`;
|
|
21
|
+
const res = await fetchUrl(httpUrl, { agent, timeoutMs: 8_000, maxRedirects: 5 });
|
|
22
|
+
httpRedirectsToHttps = res.finalUrl.startsWith("https:");
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
httpRedirectsToHttps = false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const host = u.hostname;
|
|
30
|
+
const alt = host.startsWith("www.") ? host.slice(4) : `www.${host}`;
|
|
31
|
+
const altUrl = `${u.protocol}//${alt}/`;
|
|
32
|
+
const a = await fetchUrl(`${origin}/`, {
|
|
33
|
+
agent,
|
|
34
|
+
timeoutMs: 8_000,
|
|
35
|
+
maxRedirects: 0,
|
|
36
|
+
});
|
|
37
|
+
const b = await fetchUrl(altUrl, { agent, timeoutMs: 8_000, maxRedirects: 0 });
|
|
38
|
+
const a200 = a.statusCode === 200;
|
|
39
|
+
const b200 = b.statusCode === 200;
|
|
40
|
+
wwwSplit = a200 && b200;
|
|
41
|
+
if (!wwwSplit) {
|
|
42
|
+
wwwPreferred = a200 ? origin : b200 ? `${u.protocol}//${alt}` : origin;
|
|
43
|
+
}
|
|
44
|
+
hsts = a.headers["strict-transport-security"] ?? null;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
wwwSplit = null;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const withSlash = `${origin}/probe-slash-test/`;
|
|
51
|
+
const noSlash = `${origin}/probe-slash-test`;
|
|
52
|
+
const a = await fetchUrl(withSlash, { agent, timeoutMs: 8_000, maxRedirects: 0 });
|
|
53
|
+
const b = await fetchUrl(noSlash, { agent, timeoutMs: 8_000, maxRedirects: 0 });
|
|
54
|
+
trailingSlashSplit = a.statusCode === 200 && b.statusCode === 200;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
trailingSlashSplit = null;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const slug = randomBytes(16).toString("hex");
|
|
61
|
+
randomSoft404Url = `${origin}/${slug}`;
|
|
62
|
+
const res = await fetchUrl(randomSoft404Url, {
|
|
63
|
+
agent,
|
|
64
|
+
timeoutMs: 8_000,
|
|
65
|
+
maxRedirects: 0,
|
|
66
|
+
});
|
|
67
|
+
randomSoft404 = res.statusCode === 200;
|
|
68
|
+
if (randomSoft404 && res.decoded.length) {
|
|
69
|
+
randomSoft404Hash = contentHash(res.decoded);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
randomSoft404 = false;
|
|
74
|
+
}
|
|
75
|
+
const cert = https
|
|
76
|
+
? await readCert(u.hostname, Number(u.port || 443))
|
|
77
|
+
: {
|
|
78
|
+
certValidTo: null,
|
|
79
|
+
certDaysRemaining: null,
|
|
80
|
+
certError: null,
|
|
81
|
+
alpn: null,
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
https,
|
|
85
|
+
httpRedirectsToHttps,
|
|
86
|
+
wwwSplit,
|
|
87
|
+
wwwPreferred,
|
|
88
|
+
trailingSlashSplit,
|
|
89
|
+
randomSoft404,
|
|
90
|
+
randomSoft404Url,
|
|
91
|
+
randomSoft404Hash,
|
|
92
|
+
hsts,
|
|
93
|
+
...cert,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function readCert(host, port) {
|
|
97
|
+
return new Promise((resolve) => {
|
|
98
|
+
const sock = tls.connect({ host, port, servername: host, rejectUnauthorized: true }, () => {
|
|
99
|
+
const cert = sock.getPeerCertificate();
|
|
100
|
+
const validTo = cert.valid_to ? new Date(cert.valid_to) : null;
|
|
101
|
+
const days = validTo
|
|
102
|
+
? Math.floor((validTo.getTime() - Date.now()) / 86_400_000)
|
|
103
|
+
: null;
|
|
104
|
+
const alpn = sock.alpnProtocol || sock.getProtocol() || null;
|
|
105
|
+
sock.end();
|
|
106
|
+
resolve({
|
|
107
|
+
certValidTo: validTo?.toISOString() ?? null,
|
|
108
|
+
certDaysRemaining: days,
|
|
109
|
+
certError: null,
|
|
110
|
+
alpn: typeof alpn === "string" ? alpn : null,
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
sock.setTimeout(5000, () => {
|
|
114
|
+
sock.destroy();
|
|
115
|
+
resolve({
|
|
116
|
+
certValidTo: null,
|
|
117
|
+
certDaysRemaining: null,
|
|
118
|
+
certError: "timeout",
|
|
119
|
+
alpn: null,
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
sock.on("error", (e) => {
|
|
123
|
+
resolve({
|
|
124
|
+
certValidTo: null,
|
|
125
|
+
certDaysRemaining: null,
|
|
126
|
+
certError: e.message,
|
|
127
|
+
alpn: null,
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=probe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe.js","sourceRoot":"","sources":["../src/probe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type ReadabilityResult = {
|
|
2
|
+
title: string;
|
|
3
|
+
text: string;
|
|
4
|
+
wordCount: number;
|
|
5
|
+
};
|
|
6
|
+
export declare function extractMainContent(html: string, documentTitle: string): ReadabilityResult;
|
|
7
|
+
export declare function words(text: string): number;
|
|
8
|
+
export declare function loremIpsumIn(text: string): boolean;
|
|
9
|
+
//# sourceMappingURL=readability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readability.d.ts","sourceRoot":"","sources":["../src/readability.ts"],"names":[],"mappings":"AA0BA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,GACpB,iBAAiB,CA0BnB;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG1C;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIlD"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vendored main-content extractor.
|
|
3
|
+
*
|
|
4
|
+
* @mozilla/readability 0.6.0 last published 2025-03-03 (stale). We vendor a
|
|
5
|
+
* focused extractor rather than taking the unmaintained runtime dep.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: Mozilla Readability sources `title` from attacker-controlled
|
|
8
|
+
* JSON-LD `headline` and `excerpt` from `og:description`. Both MUST be
|
|
9
|
+
* treated as untrusted. This implementation never reads JSON-LD or Open
|
|
10
|
+
* Graph for title/excerpt.
|
|
11
|
+
*/
|
|
12
|
+
import { parseHTML } from "linkedom";
|
|
13
|
+
const STRIP = new Set([
|
|
14
|
+
"SCRIPT",
|
|
15
|
+
"STYLE",
|
|
16
|
+
"NOSCRIPT",
|
|
17
|
+
"NAV",
|
|
18
|
+
"FOOTER",
|
|
19
|
+
"ASIDE",
|
|
20
|
+
"FORM",
|
|
21
|
+
"IFRAME",
|
|
22
|
+
"SVG",
|
|
23
|
+
"HEADER",
|
|
24
|
+
]);
|
|
25
|
+
export function extractMainContent(html, documentTitle) {
|
|
26
|
+
let text = "";
|
|
27
|
+
try {
|
|
28
|
+
const { document } = parseHTML(html);
|
|
29
|
+
for (const el of document.querySelectorAll("*")) {
|
|
30
|
+
if (STRIP.has(el.tagName))
|
|
31
|
+
el.remove();
|
|
32
|
+
}
|
|
33
|
+
const main = document.querySelector("article") ??
|
|
34
|
+
document.querySelector("main") ??
|
|
35
|
+
document.querySelector("[role='main']") ??
|
|
36
|
+
document.querySelector("body");
|
|
37
|
+
text = (main?.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
text = html
|
|
41
|
+
.replace(/<script[\s\S]*?<\/script>/gi, " ")
|
|
42
|
+
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
|
43
|
+
.replace(/<[^>]+>/g, " ")
|
|
44
|
+
.replace(/\s+/g, " ")
|
|
45
|
+
.trim();
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
title: documentTitle,
|
|
49
|
+
text,
|
|
50
|
+
wordCount: words(text),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function words(text) {
|
|
54
|
+
const m = text.trim().match(/\S+/g);
|
|
55
|
+
return m ? m.length : 0;
|
|
56
|
+
}
|
|
57
|
+
export function loremIpsumIn(text) {
|
|
58
|
+
return /lorem ipsum|dolor sit amet|placeholder text|\bTODO\b|\bFIXME\b|\[insert/i.test(text);
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=readability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readability.js","sourceRoot":"","sources":["../src/readability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC;IACpB,QAAQ;IACR,OAAO;IACP,UAAU;IACV,KAAK;IACL,QAAQ;IACR,OAAO;IACP,MAAM;IACN,QAAQ;IACR,KAAK;IACL,QAAQ;CACT,CAAC,CAAC;AAQH,MAAM,UAAU,kBAAkB,CAChC,IAAY,EACZ,aAAqB;IAErB,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QACrC,KAAK,MAAM,EAAE,IAAI,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;YAChD,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC;gBAAE,EAAE,CAAC,MAAM,EAAE,CAAC;QACzC,CAAC;QACD,MAAM,IAAI,GACR,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAC;YACjC,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC;YAC9B,QAAQ,CAAC,aAAa,CAAC,eAAe,CAAC;YACvC,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,GAAG,CAAC,IAAI,EAAE,WAAW,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,IAAI;aACR,OAAO,CAAC,6BAA6B,EAAE,GAAG,CAAC;aAC3C,OAAO,CAAC,2BAA2B,EAAE,GAAG,CAAC;aACzC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC;aACxB,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;aACpB,IAAI,EAAE,CAAC;IACZ,CAAC;IACD,OAAO;QACL,KAAK,EAAE,aAAa;QACpB,IAAI;QACJ,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC;KACvB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,IAAY;IAChC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACpC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,0EAA0E,CAAC,IAAI,CACpF,IAAI,CACL,CAAC;AACJ,CAAC"}
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PageExtract } from "./types.js";
|
|
2
|
+
export type RenderResult = {
|
|
3
|
+
html: string;
|
|
4
|
+
extract: PageExtract;
|
|
5
|
+
finalUrl: string;
|
|
6
|
+
errors: string[];
|
|
7
|
+
timedOut: boolean;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Adaptive Playwright renderer. Playwright is optional — if Chromium is not
|
|
11
|
+
* installed we skip JS rendering rather than failing the audit.
|
|
12
|
+
*/
|
|
13
|
+
export declare function renderPage(url: string, rawHtml: string): Promise<RenderResult | null>;
|
|
14
|
+
export declare function jsDependencyScore(html: string, extract: PageExtract): number;
|
|
15
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG9C,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CA6D9B;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,MAAM,CAQ5E"}
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { extractPage } from "./extract.js";
|
|
2
|
+
/**
|
|
3
|
+
* Adaptive Playwright renderer. Playwright is optional — if Chromium is not
|
|
4
|
+
* installed we skip JS rendering rather than failing the audit.
|
|
5
|
+
*/
|
|
6
|
+
export async function renderPage(url, rawHtml) {
|
|
7
|
+
let playwright;
|
|
8
|
+
try {
|
|
9
|
+
playwright = await import("playwright");
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const browserType = playwright.chromium;
|
|
15
|
+
let browser;
|
|
16
|
+
try {
|
|
17
|
+
browser = await browserType.launch({
|
|
18
|
+
headless: true,
|
|
19
|
+
args: [
|
|
20
|
+
"--disable-dev-shm-usage",
|
|
21
|
+
"--disable-gpu",
|
|
22
|
+
"--disable-extensions",
|
|
23
|
+
"--mute-audio",
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
const context = await browser.newContext({
|
|
27
|
+
viewport: { width: 412, height: 823 },
|
|
28
|
+
deviceScaleFactor: 2.625,
|
|
29
|
+
isMobile: true,
|
|
30
|
+
});
|
|
31
|
+
const page = await context.newPage();
|
|
32
|
+
await page.route("**/*", (route) => {
|
|
33
|
+
const type = route.request().resourceType();
|
|
34
|
+
if (type === "image" || type === "media" || type === "font") {
|
|
35
|
+
return route.abort();
|
|
36
|
+
}
|
|
37
|
+
return route.continue();
|
|
38
|
+
});
|
|
39
|
+
const errors = [];
|
|
40
|
+
page.on("pageerror", (e) => errors.push(e.message));
|
|
41
|
+
let timedOut = false;
|
|
42
|
+
try {
|
|
43
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 15_000 });
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
timedOut = true;
|
|
47
|
+
}
|
|
48
|
+
const html = await page.content();
|
|
49
|
+
const finalUrl = page.url();
|
|
50
|
+
await context.close();
|
|
51
|
+
return {
|
|
52
|
+
html,
|
|
53
|
+
extract: extractPage(html, finalUrl),
|
|
54
|
+
finalUrl,
|
|
55
|
+
errors,
|
|
56
|
+
timedOut,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return {
|
|
61
|
+
html: rawHtml,
|
|
62
|
+
extract: extractPage(rawHtml, url),
|
|
63
|
+
finalUrl: url,
|
|
64
|
+
errors: ["playwright_unavailable"],
|
|
65
|
+
timedOut: false,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
if (browser)
|
|
70
|
+
await browser.close().catch(() => undefined);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function jsDependencyScore(html, extract) {
|
|
74
|
+
let score = 0;
|
|
75
|
+
if (extract.spaRootEmpty)
|
|
76
|
+
score += 0.5;
|
|
77
|
+
if (extract.hasNoscriptJsWarning)
|
|
78
|
+
score += 0.3;
|
|
79
|
+
if (extract.mainWordCount < 50)
|
|
80
|
+
score += 0.2;
|
|
81
|
+
if (extract.scripts.filter((s) => s.src).length >= 8)
|
|
82
|
+
score += 0.1;
|
|
83
|
+
if (/#\/|#!/.test(html))
|
|
84
|
+
score += 0.2;
|
|
85
|
+
return Math.min(1, score);
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
|
package/dist/robots.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RobotsOutcome } from "./types.js";
|
|
2
|
+
export declare function parseRobotsTxt(raw: string): Omit<RobotsOutcome, "statusCode" | "contentType" | "bytes" | "redirectHops" | "error" | "mode">;
|
|
3
|
+
export declare function robotsFromFetch(opts: {
|
|
4
|
+
statusCode: number | null;
|
|
5
|
+
raw: string;
|
|
6
|
+
contentType: string;
|
|
7
|
+
bytes: number;
|
|
8
|
+
redirectHops: number;
|
|
9
|
+
error: string | null;
|
|
10
|
+
}): RobotsOutcome;
|
|
11
|
+
export declare function isAllowedByRobots(robots: RobotsOutcome, url: string, userAgent?: string): boolean;
|
|
12
|
+
export declare function matchRule(pattern: string, path: string): boolean;
|
|
13
|
+
//# sourceMappingURL=robots.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAe,aAAa,EAAE,MAAM,YAAY,CAAC;AAI7D,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,GACV,IAAI,CACL,aAAa,EACb,YAAY,GAAG,aAAa,GAAG,OAAO,GAAG,cAAc,GAAG,OAAO,GAAG,MAAM,CAC3E,CA8FA;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,GAAG,aAAa,CAoBhB;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,MAAM,EACX,SAAS,SAAY,GACpB,OAAO,CAOT;AAoCD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAchE"}
|