polite-crawler 0.1.0 → 0.1.2
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 +601 -12
- package/build/browser-CKRNbIDT.mjs +62 -0
- package/build/browser-CKRNbIDT.mjs.map +1 -0
- package/build/cli.mjs +801 -120
- package/build/cli.mjs.map +1 -1
- package/build/diff-Dk2eSvi-.mjs +648 -0
- package/build/diff-Dk2eSvi-.mjs.map +1 -0
- package/build/duration-iIuMMofi.mjs +38 -0
- package/build/duration-iIuMMofi.mjs.map +1 -0
- package/build/extract-DcwNrTK7.mjs +1793 -0
- package/build/extract-DcwNrTK7.mjs.map +1 -0
- package/build/{html-nWYFVsRU.d.mts → html-BACljlkf.d.mts} +8 -9
- package/build/{html-oNpGAHwi.mjs → html-DkQUAE_d.mjs} +18 -39
- package/build/html-DkQUAE_d.mjs.map +1 -0
- package/build/html.d.mts +2 -1
- package/build/html.mjs +3 -1
- package/build/index.d.mts +106 -6
- package/build/index.mjs +14 -6
- package/build/log-Dx-wXjJl.mjs +342 -0
- package/build/log-Dx-wXjJl.mjs.map +1 -0
- package/build/mcp.d.mts +7 -4
- package/build/mcp.mjs +561 -35
- package/build/mcp.mjs.map +1 -1
- package/build/open-Ca1lcWR3.d.mts +2875 -0
- package/build/open-DnrWgkC0.mjs +3161 -0
- package/build/open-DnrWgkC0.mjs.map +1 -0
- package/build/{politeness-C5bZ1OKM.d.mts → politeness-yI3xBEx9.d.mts} +95 -2
- package/build/politeness.d.mts +2 -2
- package/build/politeness.mjs +102 -1
- package/build/politeness.mjs.map +1 -1
- package/build/{browser-gJ_h4AhB.mjs → render-C1CUraWF.mjs} +31 -74
- package/build/render-C1CUraWF.mjs.map +1 -0
- package/build/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/build/{open-CJR4b44v.mjs → runtime-k8mo1_7U.mjs} +1018 -1582
- package/build/runtime-k8mo1_7U.mjs.map +1 -0
- package/build/urls-BHFeSyEK.mjs +62 -0
- package/build/urls-BHFeSyEK.mjs.map +1 -0
- package/build/urls-BTDSgDLC.d.mts +16 -0
- package/build/wacz-sfOLT8e9.mjs +275 -0
- package/build/wacz-sfOLT8e9.mjs.map +1 -0
- package/build/warc-VIPscWxY.mjs +728 -0
- package/build/warc-VIPscWxY.mjs.map +1 -0
- package/build/webbotauth-CjJdTAfE.mjs +568 -0
- package/build/webbotauth-CjJdTAfE.mjs.map +1 -0
- package/package.json +1 -1
- package/record.schema.json +20 -1
- package/build/browser-gJ_h4AhB.mjs.map +0 -1
- package/build/html-oNpGAHwi.mjs.map +0 -1
- package/build/open-CJR4b44v.mjs.map +0 -1
- package/build/open-CZqwY7GH.d.mts +0 -1461
- package/build/version-rVx8_y1_.mjs +0 -11
- package/build/version-rVx8_y1_.mjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "polite-crawler",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"mcpName": "io.github.mark1russell7/polite-crawler",
|
|
6
6
|
"description": "A polite web crawler for research and AI agents. It fetches a source once, keeps it, and reads it many times: robots.txt obeyed, one request per site, full-text search, an MCP server.",
|
|
7
7
|
"keywords": [
|
package/record.schema.json
CHANGED
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"type": "string"
|
|
75
75
|
},
|
|
76
76
|
"recipe": {
|
|
77
|
-
"description": "The recipe of the request: page, robots.txt, or a recipe of an application.",
|
|
77
|
+
"description": "The recipe of the request: page, robots.txt, sitemap, feed, archive-copy (a copy from a web archive), archive-index (an index of a web archive), or a recipe of an application.",
|
|
78
78
|
"type": "string"
|
|
79
79
|
},
|
|
80
80
|
"mode": { "enum": ["api", "page"] },
|
|
@@ -105,6 +105,11 @@
|
|
|
105
105
|
},
|
|
106
106
|
"etag": { "$ref": "#/$defs/nullableString" },
|
|
107
107
|
"last_modified": { "$ref": "#/$defs/nullableString" },
|
|
108
|
+
"validators": {
|
|
109
|
+
"description": "The validators that a conditional request sent. The validator memo of the host can leave out if-none-match.",
|
|
110
|
+
"type": "array",
|
|
111
|
+
"items": { "enum": ["if-none-match", "if-modified-since"] }
|
|
112
|
+
},
|
|
108
113
|
"revisit_of": {
|
|
109
114
|
"description": "The capture whose body a 304 confirmed.",
|
|
110
115
|
"type": "string"
|
|
@@ -139,6 +144,20 @@
|
|
|
139
144
|
"type": "string"
|
|
140
145
|
},
|
|
141
146
|
"final_url": { "description": "The URL that a render ended on.", "type": "string" },
|
|
147
|
+
"range": {
|
|
148
|
+
"description": "The byte range of a range request. The body is a part of the resource, not its stored copy.",
|
|
149
|
+
"type": "string",
|
|
150
|
+
"pattern": "^bytes=[0-9]+-[0-9]+$"
|
|
151
|
+
},
|
|
152
|
+
"signed": {
|
|
153
|
+
"description": "The request had a Web Bot Auth signature: the key id (a JWK thumbprint) and the URL of the key directory. The record holds no key.",
|
|
154
|
+
"type": "object",
|
|
155
|
+
"required": ["key_id", "agent"],
|
|
156
|
+
"properties": {
|
|
157
|
+
"key_id": { "type": "string" },
|
|
158
|
+
"agent": { "type": "string" }
|
|
159
|
+
}
|
|
160
|
+
},
|
|
142
161
|
"subrequests": {
|
|
143
162
|
"description": "The requests of a rendered page that the crawler allowed and refused.",
|
|
144
163
|
"type": "object",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"browser-gJ_h4AhB.mjs","names":[],"sources":["../src/render.ts","../src/browser.ts"],"sourcesContent":["import type { APIResponse, Browser, BrowserContext, Page, Request, Route } from \"playwright-core\";\n\n/**\n * The renderer opens the headless browser only for a page that shows almost nothing without\n * JavaScript. The settings:\n *\n * - The default `chromium-headless-shell` of Playwright, the lighter build.\n * - One browser for many pages, and a new context for each page. Thus no cookie or storage goes\n * from one site to the next. A context has its own HTTP cache, so routing each request costs\n * no cache across sites.\n * - Each request of the page goes through a route, the first navigation too. The route refuses\n * images, fonts and media by their resource type. It asks `allowRequest` about each other\n * request, and it sends `headers` with it.\n * - Playwright routes only the first request of a redirect chain. Thus with `allowRequest`, the\n * route sends each request itself, follows each redirect itself and asks about each step. The\n * browser then gets each body whole, not as a stream.\n * - No download, no popup, no service worker and no dialog.\n * - A wait until the visible text stops growing, with a limit. Playwright discourages\n * `networkidle`. The text includes open shadow roots and the frames of the same origin.\n * - One deadline for the whole render: the navigation, the wait and the reading of the page.\n * - After a crash or a disconnection, the next page launches a new browser.\n * - The browser closes after two idle minutes. An idle Chromium holds hundreds of megabytes, so\n * it stays open only while pages need it. The open browser keeps the process alive, so a\n * script uses `close()` when it is done.\n */\n\nexport interface Rendered {\n readonly html: string;\n readonly finalUrl: string;\n readonly status: number | null;\n readonly headers: Readonly<Record<string, string>>;\n}\n\n/** These are the options of one render. */\nexport interface RenderOptions {\n readonly userAgent: string;\n /** This value is the deadline of the whole render, in milliseconds. */\n readonly timeoutMs: number;\n /** These headers go with each request of the page, for example the `From` header. */\n readonly headers?: Readonly<Record<string, string>> | undefined;\n /**\n * This function decides each request that the page makes, the first navigation included. When\n * it gives false, the browser does not send the request. The crawler uses it to apply the\n * egress policy and robots.txt to the requests of the page.\n */\n readonly allowRequest?: ((url: string) => Promise<boolean> | boolean) | undefined;\n /** This value is the maximum number of redirects of one request of the page. The default is 5, as for a plain fetch. */\n readonly maxRedirects?: number | undefined;\n /** When this signal aborts, the renderer stops the page: it closes the context, and the render rejects. */\n readonly signal?: AbortSignal | undefined;\n}\n\nexport interface Renderer {\n render(url: string, options: RenderOptions): Promise<Rendered>;\n close(): Promise<void>;\n}\n\n/** The reason of a `RenderRefused`. */\nexport type RefusedReason = \"refused\" | \"download\" | \"scheme\" | \"browser\";\n\n/**\n * This error tells that the renderer did not render the page, and that the page or the site is\n * not at fault. The reason is one of these:\n *\n * - `refused`: `allowRequest` refused the first navigation, a step of its redirects, or a later\n * navigation of the page that left it on an error page. `url` names the refused URL.\n * - `download`: the URL gives a file, not a page.\n * - `scheme`: the URL is not an http or https URL.\n * - `browser`: the renderer has no browser (`playwright-core` or its browser is not installed). The\n * crawler then renders no more pages, and gives each one the note at once.\n *\n * A crawler does not count this error as a failure of the host.\n */\nexport class RenderRefused extends Error {\n readonly reason: RefusedReason;\n /** This URL is the request that `allowRequest` or the limit of redirects refused, when the reason is `refused`. */\n readonly url: string | undefined;\n constructor(reason: RefusedReason, message: string, url?: string) {\n super(message);\n this.name = \"RenderRefused\";\n this.reason = reason;\n this.url = url;\n }\n}\n\n/** The browser does not ask for these. The text of a page needs none of them. */\nconst BLOCKED_TYPES = new Set([\"image\", \"font\", \"media\"]);\n/** These statuses send the request on to the URL in `Location` (the Fetch standard). */\nconst REDIRECTS = new Set([301, 302, 303, 307, 308]);\nconst MAX_REDIRECTS = 5;\n\n/** The text is stable when it does not grow for this time. */\nconst SETTLED_MS = 1000;\nconst POLL_MS = 250;\nconst SETTLE_CAP_MS = 12_000;\n/** A page with no text after this time has nothing more to show. */\nconst EMPTY_SETTLE_MS = 3000;\n/** The part of the deadline that is kept to read the page after the wait. */\nconst READ_RESERVE_MS = 2000;\n/** A context that does not close in this time takes its browser with it. */\nconst CLOSE_MS = 5000;\n/** A browser that does not open a context in this time is replaced. */\nconst OPEN_MS = 5000;\n\n/** The text of the error when the optional package `playwright-core` is not installed. */\nexport const NO_PLAYWRIGHT =\n \"rendering needs the package playwright-core and a browser: npm install playwright-core, then npx playwright-core install chromium-headless-shell. For the MCP server with npx, start it with npx -y -p polite-crawler -p playwright-core polite-crawler mcp\";\n\n// playwright-core is an optional peer: it loads only when a page first needs a browser\nconst launch = async (): Promise<Browser> => {\n let chromium: typeof import(\"playwright-core\").chromium;\n try {\n ({ chromium } = await import(\"playwright-core\"));\n } catch (cause) {\n throw new Error(NO_PLAYWRIGHT, { cause });\n }\n return chromium.launch({ headless: true });\n};\n\n/** This script gives the length of the text that the page shows: its body, its open shadow roots and its frames of the same origin. */\nconst MEASURE = `(() => {\n let total = document.body ? document.body.innerText.length : 0;\n const visit = (root) => {\n for (const element of root.querySelectorAll(\"*\")) {\n if (element.shadowRoot) {\n total += (element.shadowRoot.textContent || \"\").length;\n visit(element.shadowRoot);\n }\n }\n };\n visit(document);\n for (const frame of document.querySelectorAll(\"iframe, frame\")) {\n try {\n const inner = frame.contentDocument;\n if (inner && inner.body) total += inner.body.innerText.length;\n } catch (error) {}\n }\n return total;\n})()`;\n\n/** This script gives the HTML of the page with its open shadow roots, and the body of each frame of the same origin. */\nconst SERIALIZE = `(() => {\n const roots = [];\n const visit = (root) => {\n for (const element of root.querySelectorAll(\"*\")) {\n if (element.shadowRoot) {\n roots.push(element.shadowRoot);\n visit(element.shadowRoot);\n }\n }\n };\n visit(document);\n const root = document.documentElement;\n if (!root) return { html: \"\", frames: [] };\n const quote = (value) => value.replace(/&/g, \"&\").replace(/\"/g, \""\");\n const open = \"<html\" + Array.from(root.attributes, (item) => \" \" + item.name + '=\"' + quote(item.value) + '\"').join(\"\") + \">\";\n const inner =\n roots.length > 0 && typeof root.getHTML === \"function\"\n ? root.getHTML({ serializableShadowRoots: true, shadowRoots: roots })\n : root.innerHTML;\n const frames = [];\n for (const frame of document.querySelectorAll(\"iframe, frame\")) {\n try {\n const doc = frame.contentDocument;\n if (doc && doc.body) frames.push({ src: frame.src || \"\", html: doc.body.innerHTML });\n } catch (error) {}\n }\n return { html: \"<!DOCTYPE html>\" + open + inner + \"</html>\", frames };\n})()`;\n\ninterface Serialized {\n readonly html: string;\n readonly frames: ReadonlyArray<{ readonly src: string; readonly html: string }>;\n}\n\n/** This function puts the body of each frame at the end of the page, in a section that names the frame. */\nconst withFrames = ({ html, frames }: Serialized): string => {\n if (frames.length === 0) return html;\n const sections = frames\n .map(\n ({ src, html: body }) =>\n `<section data-polite-frame=\"${src.replace(/&/g, \"&\").replace(/\"/g, \""\")}\">${body}</section>`,\n )\n .join(\"\");\n const end = html.toLowerCase().lastIndexOf(\"</body>\");\n return end === -1 ? `${html}${sections}` : `${html.slice(0, end)}${sections}${html.slice(end)}`;\n};\n\nconst messageOf = (error: unknown): string =>\n error instanceof Error ? error.message : String(error);\n\nconst sleep = (ms: number): Promise<void> =>\n new Promise((resolve) => {\n setTimeout(resolve, Math.max(0, ms)).unref();\n });\n\n/** This function gives the value of a promise, or undefined when the time ends first. */\nconst within = async <T>(promise: Promise<T>, ms: number): Promise<T | undefined> => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const late = new Promise<undefined>((resolve) => {\n timer = setTimeout(() => resolve(undefined), Math.max(0, ms));\n });\n try {\n return await Promise.race([promise, late]);\n } finally {\n clearTimeout(timer);\n }\n};\n\nexport interface PlaywrightRenderer extends Renderer {\n /** This function tells if a browser is open. */\n readonly open: () => boolean;\n}\n\nexport const playwrightRenderer = (\n options: {\n readonly idleMs?: number;\n /**\n * This function starts the browser: it gives a `Browser` of `playwright-core`. The default is\n * the headless shell of Playwright. The type names no type of `playwright-core`, so a program\n * with no `playwright-core` can check its types.\n */\n readonly launch?: (() => Promise<object>) | undefined;\n } = {},\n): PlaywrightRenderer => {\n const idleMs = options.idleMs ?? 120_000;\n const start = (options.launch ?? launch) as () => Promise<Browser>;\n let browser: Promise<Browser> | undefined;\n let inFlight = 0;\n let idle: ReturnType<typeof setTimeout> | undefined;\n const shutSoon = (): void => {\n if (idle !== undefined) clearTimeout(idle);\n idle = setTimeout(() => {\n idle = undefined;\n if (inFlight > 0 || browser === undefined) return;\n const closing = browser;\n browser = undefined;\n void closing.then((running) => running.close()).catch(() => undefined);\n }, idleMs);\n // the timer alone does not keep the process alive; the open browser does\n idle.unref();\n };\n const acquire = async (): Promise<Browser> => {\n for (let attempt = 0; ; attempt += 1) {\n if (browser === undefined) {\n const launching: Promise<Browser> = start().then((running) => {\n // a crash, a kill or a closed pipe: the next page launches a new browser\n running.on(\"disconnected\", () => {\n if (browser === launching) browser = undefined;\n });\n return running;\n });\n // a failed launch is not kept, so the next page tries again; a newer\n // launch that replaced this one is not touched\n launching.catch(() => {\n if (browser === launching) browser = undefined;\n });\n browser = launching;\n }\n const held = browser;\n const running = await held;\n if (running.isConnected() || attempt > 0) return running;\n if (browser === held) browser = undefined;\n }\n };\n /** This function gives up on a browser that does not answer. The next page launches a new one. */\n const forget = (running: Browser): void => {\n const held = browser;\n if (held !== undefined) {\n void held.then(\n (current) => {\n if (current === running && browser === held) browser = undefined;\n },\n () => undefined,\n );\n }\n void running.close().catch(() => undefined);\n };\n\n const renderPage = async (\n url: string,\n {\n userAgent,\n timeoutMs,\n headers,\n allowRequest,\n maxRedirects = MAX_REDIRECTS,\n signal,\n }: RenderOptions,\n ): Promise<Rendered> => {\n if (signal?.aborted === true)\n throw signal.reason instanceof Error ? signal.reason : new Error(\"the render was cancelled\");\n const deadline = Date.now() + timeoutMs;\n const left = (): number => Math.max(1, deadline - Date.now());\n let running: Browser | undefined;\n let context: BrowserContext | undefined;\n let shownPage: Page | undefined;\n /** The last navigation of the page that the route refused: the reason, and the refused URL. */\n let refused: { readonly message: string; readonly url: string } | undefined;\n /** The last navigation of the page that the route answered: the URL that the browser asked for, and the URL of the answer. */\n let navigated: { readonly asked: string; readonly final: string } | undefined;\n const extra = Object.fromEntries(\n Object.entries(headers ?? {}).map(([name, value]) => [name.toLowerCase(), value]),\n );\n const contextOptions = {\n userAgent,\n serviceWorkers: \"block\" as const,\n acceptDownloads: false,\n viewport: { width: 1280, height: 900 },\n };\n const ask = async (url: string): Promise<boolean> =>\n allowRequest === undefined\n ? true\n : Promise.resolve()\n .then(() => allowRequest(url))\n .catch(() => false);\n /**\n * This function sends one request of the page and follows its redirects. Each step is a new\n * decision of `allowRequest`, and a step past `maxRedirects` is refused. It gives the answer,\n * or the reason of a refusal.\n *\n * These rules are the rules of a browser. Each step gets the cookies of its own URL from the\n * context. A credential does not go to a different origin. A 303, or a 301 or 302 after a POST,\n * changes the request to a GET.\n */\n const follow = async (\n route: Route,\n sent: Record<string, string> | undefined,\n ): Promise<\n | { readonly response: APIResponse; readonly url: string }\n | { readonly refusal: string; readonly url: string }\n > => {\n const request = route.request();\n let url = request.url();\n let method = request.method();\n let headers: Record<string, string> = { ...request.headers(), ...extra };\n let response = await route.fetch({\n maxRedirects: 0,\n timeout: left(),\n ...(sent === undefined ? {} : { headers: sent }),\n });\n for (let hop = 0; ; hop += 1) {\n const location = REDIRECTS.has(response.status())\n ? response.headers()[\"location\"]\n : undefined;\n if (location === undefined) return { response, url };\n let next: URL;\n try {\n next = new URL(location, url);\n } catch {\n return { refusal: `${url} redirects to a URL that does not parse`, url };\n }\n if (hop >= maxRedirects) {\n return {\n refusal: `${request.url()} redirects more than ${String(maxRedirects)} times`,\n url: next.href,\n };\n }\n if (!(await ask(next.href))) {\n return {\n refusal: `the request for ${next.href}, a redirect of ${url}, was refused`,\n url: next.href,\n };\n }\n const status = response.status();\n const get = status === 303 || ((status === 301 || status === 302) && method === \"POST\");\n const crossOrigin = next.origin !== new URL(url).origin;\n headers = Object.fromEntries(\n Object.entries(headers).filter(\n ([name]) =>\n name !== \"cookie\" &&\n !(crossOrigin && name === \"authorization\") &&\n !(get && (name === \"content-type\" || name === \"content-length\")),\n ),\n );\n if (get) method = \"GET\";\n const body = get ? undefined : (request.postDataBuffer() ?? undefined);\n response = await context!.request.fetch(next.href, {\n method,\n headers,\n maxRedirects: 0,\n timeout: left(),\n ...(body === undefined ? {} : { data: body }),\n });\n url = next.href;\n }\n };\n /** This function tells if a request is a navigation of the page itself, not of a frame or a popup. */\n const ofPage = (request: Request): boolean => {\n if (!request.isNavigationRequest()) return false;\n try {\n return request.frame().parentFrame() === null && request.frame().page() === shownPage;\n } catch {\n // a popup asks for its first page before it has a frame\n return false;\n }\n };\n const decide = async (route: Route): Promise<void> => {\n const request = route.request();\n const main = ofPage(request);\n try {\n if (BLOCKED_TYPES.has(request.resourceType())) {\n await route.abort(\"blockedbyclient\");\n return;\n }\n if (!(await ask(request.url()))) {\n if (main)\n refused = {\n message: `the request for ${request.url()} was refused`,\n url: request.url(),\n };\n await route.abort(\"blockedbyclient\");\n return;\n }\n const sent =\n Object.keys(extra).length === 0\n ? undefined\n : {\n ...Object.fromEntries(\n Object.entries(await request.allHeaders()).filter(\n ([name]) => !name.startsWith(\":\") && name !== \"host\",\n ),\n ),\n ...extra,\n };\n if (allowRequest === undefined) {\n await route.continue(sent === undefined ? undefined : { headers: sent });\n return;\n }\n const answer = await follow(route, sent);\n if (\"refusal\" in answer) {\n if (main) refused = { message: answer.refusal, url: answer.url };\n await route.abort(\"blockedbyclient\");\n return;\n }\n if (main) navigated = { asked: request.url(), final: answer.url };\n await route.fulfill({ response: answer.response });\n } catch {\n // the page closed while the request waited: nothing to answer\n }\n };\n const work = (async (): Promise<Rendered> => {\n running = await acquire();\n // a browser that was killed can keep its pipe open and answer nothing:\n // it gets a few seconds, and then a new browser takes its place\n const opened = await within(\n running.newContext(contextOptions).catch((error: unknown) => {\n if (!/closed|disconnected/i.test(messageOf(error))) throw error;\n return undefined;\n }),\n Math.min(OPEN_MS, left()),\n );\n if (opened === undefined) {\n forget(running);\n running = await acquire();\n context = await running.newContext(contextOptions);\n } else {\n context = opened;\n }\n await context.route(\"**/*\", decide);\n if (allowRequest !== undefined && typeof context.routeWebSocket === \"function\") {\n await context.routeWebSocket(/.*/, async (socket) => {\n const allowed = await Promise.resolve()\n .then(() => allowRequest(socket.url().replace(/^ws/i, \"http\")))\n .catch(() => false);\n if (allowed) socket.connectToServer();\n else await socket.close({ code: 1008, reason: \"refused\" }).catch(() => undefined);\n });\n }\n const page: Page = await context.newPage();\n shownPage = page;\n // a popup is not the page, and a dialog waits for no one\n context.on(\"page\", (other) => {\n if (other !== page) void other.close().catch(() => undefined);\n });\n page.on(\"dialog\", (dialog) => {\n void dialog.dismiss().catch(() => undefined);\n });\n let response;\n try {\n response = await page.goto(url, {\n waitUntil: \"domcontentloaded\",\n timeout: Math.max(1, left() - READ_RESERVE_MS),\n });\n } catch (error) {\n if (refused !== undefined) throw new RenderRefused(\"refused\", refused.message, refused.url);\n if (/Download is starting/i.test(messageOf(error))) {\n throw new RenderRefused(\"download\", `${url} gives a file, not a page`);\n }\n throw error;\n }\n const settleEnd = Math.min(deadline - READ_RESERVE_MS, Date.now() + SETTLE_CAP_MS);\n const settleStart = Date.now();\n let last = -1;\n let since = Date.now();\n while (Date.now() < settleEnd) {\n const measured = (page.evaluate(MEASURE) as Promise<number>).catch(\n async (error: unknown) => {\n // a script of the page can start a navigation while the text is measured: the next poll measures the new document\n if (!/destroyed|navigat/i.test(messageOf(error))) throw error;\n await page\n .waitForLoadState(\"domcontentloaded\", { timeout: left() })\n .catch(() => undefined);\n // no length equals NaN, so the wait goes on\n return Number.NaN;\n },\n );\n const length = await within(measured, settleEnd - Date.now());\n // a page that keeps its thread busy does not answer: the reading below decides\n if (length === undefined) break;\n if (length !== last) {\n last = length;\n since = Date.now();\n } else if (\n Date.now() - since >= SETTLED_MS &&\n (length > 0 || Date.now() - settleStart >= EMPTY_SETTLE_MS)\n ) {\n break;\n }\n await sleep(Math.min(POLL_MS, settleEnd - Date.now()));\n }\n // a navigation can replace the document while it is read: the read waits for the new document\n // and tries again, until the deadline (a page can navigate many times in a row)\n let serialized: Serialized | undefined;\n while (serialized === undefined) {\n try {\n serialized = (await page.evaluate(SERIALIZE)) as Serialized;\n } catch (error) {\n if (!/destroyed|navigat/i.test(messageOf(error)) || Date.now() >= deadline) throw error;\n await page\n .waitForLoadState(\"domcontentloaded\", { timeout: left() })\n .catch(() => undefined);\n }\n }\n // a later navigation that the route refused leaves the browser on its error page, which is not the page\n if (refused !== undefined && page.url().startsWith(\"chrome-error:\")) {\n throw new RenderRefused(\"refused\", `the page moved: ${refused.message}`, refused.url);\n }\n // the route followed the redirects of the navigation, so the browser shows the first URL\n const shown = page.url().replace(/#.*$/s, \"\");\n return {\n html: withFrames(serialized),\n finalUrl:\n navigated !== undefined && shown === navigated.asked ? navigated.final : page.url(),\n status: response?.status() ?? null,\n headers: response?.headers() ?? {},\n };\n })();\n // the deadline can win the race; the work then ends when its context closes\n work.catch(() => undefined);\n let timer: ReturnType<typeof setTimeout> | undefined;\n let cancel: (() => void) | undefined;\n const expired = new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(new Error(`render timed out after ${String(timeoutMs)} ms`)),\n timeoutMs,\n );\n // a cancelled render ends now; the context closes below, and that stops the page\n if (signal !== undefined) {\n cancel = () =>\n reject(\n signal.reason instanceof Error ? signal.reason : new Error(\"the render was cancelled\"),\n );\n signal.addEventListener(\"abort\", cancel, { once: true });\n }\n });\n const closeContext = async (): Promise<void> => {\n const open = context;\n if (open === undefined) return;\n const closed = await within(\n open.close().then(\n () => true,\n () => true,\n ),\n CLOSE_MS,\n );\n if (closed === undefined && running !== undefined) forget(running);\n };\n try {\n return await Promise.race([work, expired]);\n } finally {\n clearTimeout(timer);\n if (cancel !== undefined) signal?.removeEventListener(\"abort\", cancel);\n if (context !== undefined) await closeContext();\n // a context made after the deadline is closed when it arrives\n else void work.then(closeContext, closeContext).catch(() => undefined);\n }\n };\n\n return {\n open: () => browser !== undefined,\n async render(url, renderOptions) {\n let target: URL;\n try {\n target = new URL(url);\n } catch {\n throw new RenderRefused(\"scheme\", `${url} is not a URL`);\n }\n if (target.protocol !== \"http:\" && target.protocol !== \"https:\") {\n throw new RenderRefused(\n \"scheme\",\n `the renderer reads http and https pages, not ${target.protocol}`,\n );\n }\n if (idle !== undefined) {\n clearTimeout(idle);\n idle = undefined;\n }\n inFlight += 1;\n try {\n return await renderPage(target.href, renderOptions);\n } finally {\n inFlight -= 1;\n if (inFlight === 0) shutSoon();\n }\n },\n async close() {\n if (idle !== undefined) clearTimeout(idle);\n idle = undefined;\n const launched = browser;\n browser = undefined;\n // a launch that failed has nothing to close, and closing never throws:\n // the idle timer calls this with no one to catch it\n if (launched !== undefined)\n await launched.then(\n (open) => open.close(),\n () => undefined,\n );\n },\n };\n};\n","import { existsSync } from \"node:fs\";\nimport { basename, dirname, join } from \"node:path\";\nimport type { Renderer } from \"./render.ts\";\nimport { NO_PLAYWRIGHT, RenderRefused } from \"./render.ts\";\n\n/** The text of the note when `playwright-core` is installed, but its browser is not. */\nexport const NO_BROWSER =\n \"rendering needs a browser: npx playwright-core install chromium-headless-shell\";\n\n/** This is the part of `playwright-core` that the check uses. */\ninterface Chromium {\n executablePath(): string;\n}\n\n/**\n * This function tells if the default renderer can start a browser. It gives no value when it can,\n * and the reason when it cannot. The command line and the MCP server use it one time at start.\n * Then a missing browser costs no wait and no error capture for each page that needs JavaScript.\n *\n * The check does not start the browser. It finds the full Chromium build or the headless shell of\n * the same revision on the disk. When the check cannot find the answer, it gives no value, and the\n * renderer then tries.\n */\nexport const browserMissing = async (\n load: () => Promise<{ readonly chromium: Chromium }> = () => import(\"playwright-core\"),\n): Promise<string | undefined> => {\n let chromium: Chromium;\n try {\n ({ chromium } = await load());\n } catch {\n return NO_PLAYWRIGHT;\n }\n try {\n const executable = chromium.executablePath();\n if (existsSync(executable)) return undefined;\n // the headless shell is in a folder next to the folder of the full build, with the same revision\n let folder = dirname(executable);\n while (dirname(folder) !== folder && !/^chromium-\\d+$/.test(basename(folder)))\n folder = dirname(folder);\n const revision = /^chromium-(\\d+)$/.exec(basename(folder))?.[1];\n if (revision === undefined) return undefined;\n return existsSync(join(dirname(folder), `chromium_headless_shell-${revision}`))\n ? undefined\n : NO_BROWSER;\n } catch {\n return undefined;\n }\n};\n\n/**\n * This function gives a renderer that checks one time, before its first page, that the browser is\n * on the disk (`browserMissing`). With no browser, each page gets a `RenderRefused` with the reason\n * `browser` at once, and the renderer opens nothing. `createRuntime` puts its default renderer in\n * it, so a library with no browser costs no wait and no error capture.\n */\nexport const checkedRenderer = (\n renderer: Renderer,\n check: () => Promise<string | undefined> = () => browserMissing(),\n): Renderer => {\n let missing: Promise<string | undefined> | undefined;\n return {\n async render(url, options) {\n missing ??= check().catch(() => undefined);\n const reason = await missing;\n if (reason !== undefined) throw new RenderRefused(\"browser\", reason);\n return renderer.render(url, options);\n },\n close: () => renderer.close(),\n };\n};\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"html-oNpGAHwi.mjs","names":[],"sources":["../src/urls.ts","../src/text.ts","../src/html.ts"],"sourcesContent":["/**\n * This module gives the canonical form of a URL. The document id of a page and the cache key of the\n * crawler use it. Thus two spellings of one page are one document and one cached answer.\n */\n\nconst TRACKING =\n /^(utm_[a-z_]+|fbclid|gclid|gclsrc|dclid|gbraid|wbraid|msclkid|yclid|ttclid|twclid|li_fat_id|srsltid|mc_[a-z]+|_ga|_gl|_hsenc|_hsmi|__hstc|__hssc|__hsfp|mkt_tok|ref_src|igshid)$/i;\n\n/** This function decodes the escapes of unreserved characters (`%7E` is `~`), and writes the other escapes in capitals. */\nconst normalizeEscapes = (value: string): string =>\n value.replace(/%[0-9a-f]{2}/gi, (escape) => {\n const char = String.fromCharCode(Number.parseInt(escape.slice(1), 16));\n return /^[A-Za-z0-9\\-._~]$/.test(char) ? char : escape.toUpperCase();\n });\n\n/**\n * This function gives one URL for each page: a lowercase host, and no fragment, tracking\n * parameters, empty query or default port. It decodes the escapes of unreserved characters.\n */\nexport const canonicalUrl = (value: string): string => {\n const url = new URL(value);\n url.hash = \"\";\n url.hostname = url.hostname.toLowerCase();\n if (\n (url.protocol === \"https:\" && url.port === \"443\") ||\n (url.protocol === \"http:\" && url.port === \"80\")\n ) {\n url.port = \"\";\n }\n // a copy of the keys, because the loop deletes from the live list\n for (const key of Array.from(url.searchParams.keys())) {\n if (TRACKING.test(key)) url.searchParams.delete(key);\n }\n if (url.pathname.includes(\"%\")) url.pathname = normalizeEscapes(url.pathname);\n // \"/p?\" is \"/p\"\n url.search = url.search.includes(\"%\") ? normalizeEscapes(url.search) : url.search;\n return url.href;\n};\n\n/**\n * This function gives the cache key of a URL: its canonical form. A text that is not a URL stays as\n * it is. The crawler and `reindex` use the same key, so a rebuilt index answers the same requests.\n */\nexport const cacheKeyOf = (value: string): string => {\n try {\n return canonicalUrl(value);\n } catch {\n return value;\n }\n};\n","/**\n * The body decoder changes the bytes of a response into text. It finds the encoding in this order,\n * as the WHATWG encoding rules and the browsers do:\n *\n * 1. A byte order mark.\n * 2. The `charset` of the `Content-Type` header.\n * 3. A `<meta charset>` or `http-equiv` declaration in the first 2,048 bytes of an HTML page, or the\n * encoding of an XML declaration.\n * 4. UTF-8 when the bytes are UTF-8, and windows-1252 when they are not.\n *\n * It uses only `TextDecoder`, so it operates in a browser too. A byte that the encoding does not\n * know becomes U+FFFD. The decoder does not throw.\n */\n\nconst PRESCAN_BYTES = 2048;\n\n/** This function gives the WHATWG name of an encoding label, or undefined when the runtime does not know the label. */\nconst encodingOf = (label: string): string | undefined => {\n try {\n return new TextDecoder(label.trim().toLowerCase()).encoding;\n } catch {\n return undefined;\n }\n};\n\nconst bomOf = (bytes: Uint8Array): string | undefined => {\n if (bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) return \"utf-8\";\n if (bytes[0] === 0xfe && bytes[1] === 0xff) return \"utf-16be\";\n if (bytes[0] === 0xff && bytes[1] === 0xfe) return \"utf-16le\";\n return undefined;\n};\n\n/** This function gives the `charset` parameter of a media type, or undefined. */\nconst charsetOf = (contentType: string | null): string | undefined => {\n if (contentType === null) return undefined;\n const match = /;\\s*charset\\s*=\\s*(?:\"([^\"]*)\"|([^\\s;\"]+))/i.exec(contentType.slice(0, 512));\n return match?.[1] ?? match?.[2] ?? undefined;\n};\n\n/** This function reads the encoding that the start of an HTML or XML document declares. */\nconst declaredCharset = (bytes: Uint8Array): string | undefined => {\n let head = \"\";\n const end = Math.min(bytes.length, PRESCAN_BYTES);\n for (let index = 0; index < end; index += 1) head += String.fromCharCode(bytes[index]!);\n const xml = /^\\s*<\\?xml[^>]*?\\bencoding\\s*=\\s*[\"']([^\"']+)[\"']/i.exec(head);\n if (xml !== null) return xml[1];\n // a declaration inside a comment declares nothing\n const markup = head.replace(/<!--[\\s\\S]*?-->/g, \"\");\n // `<meta charset=x>`, and `<meta http-equiv=\"Content-Type\" content=\"text/html; charset=x\">`\n const meta = /<meta\\b[^>]*?\\bcharset\\s*=\\s*[\"']?\\s*([A-Za-z0-9_:.+-]+)/i.exec(markup);\n return meta?.[1];\n};\n\n/**\n * This function counts the UTF-8 sequences of more than one byte, and the bytes that no UTF-8\n * sequence explains. An incomplete sequence at the end is no error: a body cut at its size limit\n * ends so.\n */\nconst utf8Evidence = (\n bytes: Uint8Array,\n): { readonly multibyte: number; readonly invalid: number } => {\n let multibyte = 0;\n let invalid = 0;\n let index = 0;\n while (index < bytes.length) {\n const lead = bytes[index]!;\n if (lead < 0x80) {\n index += 1;\n continue;\n }\n const need =\n lead >= 0xc2 && lead <= 0xdf\n ? 1\n : lead >= 0xe0 && lead <= 0xef\n ? 2\n : lead >= 0xf0 && lead <= 0xf4\n ? 3\n : 0;\n if (need === 0) {\n invalid += 1;\n index += 1;\n continue;\n }\n // the second byte has a narrower range after E0, ED, F0 and F4\n const low = lead === 0xe0 ? 0xa0 : lead === 0xf0 ? 0x90 : 0x80;\n const high = lead === 0xed ? 0x9f : lead === 0xf4 ? 0x8f : 0xbf;\n let ok = true;\n let at = 1;\n while (at <= need && index + at < bytes.length) {\n const next = bytes[index + at]!;\n if (at === 1 ? next < low || next > high : next < 0x80 || next > 0xbf) {\n ok = false;\n break;\n }\n at += 1;\n }\n // the body ends inside the sequence\n if (ok && at <= need) break;\n if (ok) {\n multibyte += 1;\n index += need + 1;\n } else {\n invalid += 1;\n index += 1;\n }\n }\n return { multibyte, invalid };\n};\n\n/**\n * This function gives the WHATWG name of the encoding of a body, for example `utf-8` or\n * `windows-1252`. The function `decodeBody` uses it.\n */\nexport const bodyEncoding = (bytes: Uint8Array, contentType: string | null): string => {\n const bom = bomOf(bytes);\n if (bom !== undefined) return bom;\n const header = charsetOf(contentType);\n const fromHeader = header === undefined ? undefined : encodingOf(header);\n if (fromHeader !== undefined) return fromHeader;\n if (contentType === null || /html|xml/i.test(contentType)) {\n const label = declaredCharset(bytes);\n let declared = label === undefined ? undefined : encodingOf(label);\n // the HTML standard reads a UTF-16 declaration in ASCII-compatible bytes as UTF-8\n if (declared?.startsWith(\"utf-16\") === true) declared = \"utf-8\";\n if (declared === \"x-user-defined\") declared = \"windows-1252\";\n if (declared === \"utf-8\") return declared;\n if (declared !== undefined) {\n // A browser serialises the page it rendered as UTF-8, and the old declaration stays. UTF-8\n // with no error is almost never a legacy encoding by chance.\n const evidence = utf8Evidence(bytes);\n return evidence.invalid === 0 && evidence.multibyte > 0 ? \"utf-8\" : declared;\n }\n }\n if (/json/i.test(contentType ?? \"\")) return \"utf-8\";\n const evidence = utf8Evidence(bytes);\n return evidence.invalid > 0 && evidence.invalid >= evidence.multibyte ? \"windows-1252\" : \"utf-8\";\n};\n\n/**\n * These are the characters of the bytes 0x80 to 0x9F in windows-1252, from the WHATWG index.\n * Node.js 22 decodes windows-1252 as ISO-8859-1, which gives a control character for each of\n * these bytes. A later runtime gives the correct characters, and then the map changes nothing.\n */\nconst WINDOWS_1252_HIGH = [\n 0x20ac, 0x81, 0x201a, 0x192, 0x201e, 0x2026, 0x2020, 0x2021, 0x2c6, 0x2030, 0x160, 0x2039, 0x152,\n 0x8d, 0x17d, 0x8f, 0x90, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x2dc, 0x2122,\n 0x161, 0x203a, 0x153, 0x9d, 0x17e, 0x178,\n];\nconst C1_CONTROLS = new RegExp(`[${String.fromCharCode(0x80)}-${String.fromCharCode(0x9f)}]`, \"g\");\nconst repairWindows1252 = (text: string): string =>\n text.replace(C1_CONTROLS, (char) =>\n String.fromCharCode(WINDOWS_1252_HIGH[char.charCodeAt(0) - 0x80]!),\n );\n\n/**\n * This function decodes the body of a response. It reads the encoding from the byte order mark,\n * the `Content-Type` header and the document, with `bodyEncoding`. It does not throw.\n */\nexport const decodeBody = (bytes: Uint8Array, contentType: string | null): string => {\n const encoding = bodyEncoding(bytes, contentType);\n try {\n const text = new TextDecoder(encoding, { fatal: false }).decode(bytes);\n return encoding === \"windows-1252\" ? repairWindows1252(text) : text;\n } catch {\n // a runtime with a small ICU knows only a few encodings\n return new TextDecoder(\"utf-8\", { fatal: false }).decode(bytes);\n }\n};\n","import { Readability } from \"@mozilla/readability\";\nimport { parseHTML } from \"linkedom\";\nimport { canonicalUrl } from \"./urls.ts\";\n\nexport { bodyEncoding, decodeBody } from \"./text.ts\";\n\n/**\n * The page reader reads a web page with no browser first. It gives:\n *\n * 1. The JSON-LD, the structured data that 41% of pages have. For a venue, it gives the name, the\n * address, the phone number and sometimes the hours.\n * 2. The visible text of the page.\n * 3. A decision: does the page need JavaScript to show its content?\n *\n * On a short page, the text is all of the visible text. Readability is for articles, and it\n * removes footers, sidebars and tables, where a venue keeps its hours. Thus on a long page, the\n * main text of Readability comes first. A blank line follows it, and then each other line of the\n * page.\n *\n * The cost of the reader grows in proportion to the size of the page. It walks the document with\n * no recursion, so deep markup cannot overflow the stack. It does not use Readability on a page\n * that is too large or too deep for it.\n */\n\nexport interface PageLink {\n readonly href: string;\n readonly text: string;\n}\n\nexport interface PageExtract {\n readonly title: string;\n readonly description: string | null;\n readonly text: string;\n /**\n * When the reader cut `text` to its maximum, this text is the whole visible text in the order of\n * the page. A quote past the cut is still in it. Otherwise this value is undefined.\n */\n readonly fullText?: string | undefined;\n /** These are the JSON-LD objects of the page, with each `@graph` made flat. */\n readonly jsonld: readonly Record<string, unknown>[];\n readonly links: readonly PageLink[];\n readonly lang: string | null;\n readonly visibleChars: number;\n /** These are the names of the application states that the HTML holds (Next.js, Nuxt, Apollo). */\n readonly embedded: readonly string[];\n /** This value is true when the page shows almost nothing without its scripts. */\n readonly needsRender: boolean;\n readonly renderReason: string | null;\n /** This URL is the `<link rel=\"canonical\">` of the page when it is on the same site, or null. */\n readonly canonical: string | null;\n /** This URL is the target of a `<meta http-equiv=\"refresh\">` that sends the reader on in 5 seconds or less. */\n readonly redirect: string | null;\n /**\n * This value names the bot check or the firewall that sent its page in place of the content, for\n * example \"Cloudflare\". A crawler must not open a browser to get past it.\n */\n readonly blocked: string | null;\n /** These notes tell what the reader left out, for example a JSON-LD block that it cannot read. */\n readonly notes: readonly string[];\n}\n\nconst SKIP = new Set([\n \"script\",\n \"style\",\n \"noscript\",\n // a template is inert; a declarative shadow root is read (see `isShadowRoot`)\n \"template\",\n \"svg\",\n \"math\",\n // never shown, and a page without a <head> leaves it among the content\n \"title\",\n \"meta\",\n \"link\",\n \"base\",\n \"iframe\",\n \"frame\",\n \"object\",\n \"embed\",\n \"canvas\",\n \"audio\",\n \"video\",\n \"datalist\",\n \"param\",\n \"source\",\n \"track\",\n]);\n/** These elements show as blocks: each starts a line. The list follows the default style sheet of HTML. */\nconst BLOCK = new Set([\n \"address\",\n \"article\",\n \"aside\",\n \"blockquote\",\n \"body\",\n \"br\",\n \"caption\",\n \"center\",\n \"dd\",\n \"details\",\n \"dialog\",\n \"dir\",\n \"div\",\n \"dl\",\n \"dt\",\n \"fieldset\",\n \"figcaption\",\n \"figure\",\n \"footer\",\n \"form\",\n \"frameset\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n // linkedom leaves the content of a page that never closes its <head> inside it\n \"head\",\n \"header\",\n \"hgroup\",\n \"hr\",\n \"html\",\n \"legend\",\n \"li\",\n \"listing\",\n \"main\",\n \"marquee\",\n \"menu\",\n \"nav\",\n \"noframes\",\n \"ol\",\n // a dropdown's choices, one to a line\n \"optgroup\",\n \"option\",\n \"p\",\n \"plaintext\",\n \"pre\",\n \"search\",\n \"section\",\n \"summary\",\n \"table\",\n \"tbody\",\n \"tfoot\",\n \"thead\",\n \"tr\",\n \"ul\",\n \"xmp\",\n]);\n/**\n * These elements run inline with the text around them. Any other element, a custom element too,\n * ends a word, so its text stays apart from the next word.\n */\nconst INLINE = new Set([\n \"a\",\n \"abbr\",\n \"acronym\",\n \"b\",\n \"bdi\",\n \"bdo\",\n \"big\",\n \"blink\",\n \"cite\",\n \"code\",\n \"data\",\n \"del\",\n \"dfn\",\n \"em\",\n \"font\",\n \"i\",\n \"img\",\n \"ins\",\n \"kbd\",\n \"label\",\n \"mark\",\n \"nobr\",\n \"picture\",\n \"q\",\n \"rb\",\n \"rp\",\n \"rt\",\n \"rtc\",\n \"ruby\",\n \"s\",\n \"samp\",\n \"slot\",\n \"small\",\n \"span\",\n \"strike\",\n \"strong\",\n \"sub\",\n \"sup\",\n \"time\",\n \"tt\",\n \"u\",\n \"var\",\n \"wbr\",\n]);\n/** A table row becomes one line (\"Sunday 10 am–5 pm\"), with a space between its cells. */\nconst CELL = new Set([\"td\", \"th\"]);\nconst WORD_END = new Set([\"a\", \"button\"]);\n\n/** A page shorter than this keeps all its text. On a longer page, the text of Readability comes first. */\nconst SHORT_PAGE_CHARS = 6000;\nconst MAX_TEXT_CHARS = 50_000;\n/** On a long page, this part of the text is kept for the lines after the article: a footer keeps its hours. */\nconst REST_RESERVE_CHARS = 12_000;\nconst MAX_LINKS = 300;\n/** Readability takes seconds on a page with more elements or links than this, and more time again on deep markup. */\nconst READABILITY_MAX_ELEMENTS = 10_000;\nconst READABILITY_MAX_LINKS = 1500;\nconst READABILITY_MAX_DEPTH = 150;\n/**\n * The parser of linkedom takes time in proportion to the square of the depth of the markup: 200,000\n * levels take 30 seconds. The reader reads the tags below this depth as text.\n */\nconst MAX_DEPTH = 1000;\n\n/** This is the small part of the DOM that this file uses. Node has no DOM types, and linkedom supplies the DOM. */\ninterface DomNode {\n readonly nodeType: number;\n readonly nodeName: string;\n readonly textContent: string | null;\n readonly childNodes: ArrayLike<DomNode>;\n readonly firstChild?: DomNode | null | undefined;\n readonly nextSibling?: DomNode | null | undefined;\n readonly parentNode?: DomNode | null | undefined;\n /** This method takes the node out of its parent. */\n readonly remove?: (() => void) | undefined;\n}\n\ninterface DomElement extends DomNode {\n getAttribute(name: string): string | null;\n querySelector(selector: string): DomElement | null;\n /** This is the content of a `<template>`. */\n readonly content?: DomNode | null | undefined;\n}\n\n/**\n * This type has no `body` or `head`, on purpose. When an element comes before `<head>`, linkedom\n * makes a new, empty pair. Then `document.body` reads nothing, but a browser shows the full page.\n */\ninterface DomDocument extends DomNode {\n querySelector(selector: string): DomElement | null;\n querySelectorAll(selector: string): ArrayLike<DomElement> & Iterable<DomElement>;\n readonly documentElement: DomElement | null;\n}\n\nconst parse = (html: string): DomDocument =>\n (parseHTML(html) as unknown as { document: DomDocument }).document;\n\n/** These elements have no end tag. */\nconst VOID = new Set([\n \"area\",\n \"base\",\n \"basefont\",\n \"bgsound\",\n \"br\",\n \"col\",\n \"embed\",\n \"frame\",\n \"hr\",\n \"img\",\n \"input\",\n \"keygen\",\n \"link\",\n \"meta\",\n \"param\",\n \"source\",\n \"track\",\n \"wbr\",\n]);\n/** The content of these elements is text up to their end tag, not markup. */\nconst RAW_TEXT = new Set([\n \"iframe\",\n \"noembed\",\n \"noframes\",\n \"noscript\",\n \"plaintext\",\n \"script\",\n \"style\",\n \"textarea\",\n \"title\",\n \"xmp\",\n]);\n/** A new element of one of these kinds closes the open one (\"<li>one<li>two\"). */\nconst IMPLIED_END = new Set([\n \"dd\",\n \"dt\",\n \"li\",\n \"optgroup\",\n \"option\",\n \"p\",\n \"rb\",\n \"rp\",\n \"rt\",\n \"rtc\",\n \"tbody\",\n \"td\",\n \"tfoot\",\n \"th\",\n \"thead\",\n \"tr\",\n]);\n\nconst isLetter = (code: number): boolean =>\n (code >= 0x61 && code <= 0x7a) || (code >= 0x41 && code <= 0x5a);\nconst isNameChar = (code: number): boolean =>\n isLetter(code) ||\n (code >= 0x30 && code <= 0x39) ||\n code === 0x2d ||\n code === 0x3a ||\n code === 0x5f ||\n code === 0x2e;\n\n/** This function gives the index after the `>` of a tag. A `>` inside a quoted attribute value does not end it. */\nconst endOfTag = (html: string, from: number): number => {\n let quote = 0;\n let afterEquals = false;\n for (let at = from; at < html.length; at += 1) {\n const code = html.charCodeAt(at);\n if (quote !== 0) {\n if (code === quote) quote = 0;\n } else if ((code === 0x22 || code === 0x27) && afterEquals) {\n quote = code;\n } else if (code === 0x3e) {\n return at + 1;\n }\n if (code === 0x3d) afterEquals = true;\n else if (code !== 0x20 && code !== 0x09 && code !== 0x0a && code !== 0x0d) afterEquals = false;\n }\n return html.length;\n};\n\n/**\n * This function reads the tags below `MAX_DEPTH` levels as text: a block tag becomes a line break\n * and another tag becomes nothing. It counts the open elements roughly, in one pass. When it\n * removes nothing, it gives the same string.\n */\nconst flattenDeepMarkup = (html: string): { readonly html: string; readonly dropped: number } => {\n const stack: string[] = [];\n const open = new Map<string, number>();\n const extra = new Map<string, number>();\n const out: string[] = [];\n let copied = 0;\n let dropped = 0;\n const popTo = (name: string): void => {\n if ((open.get(name) ?? 0) === 0) return;\n for (;;) {\n const top = stack.pop()!;\n open.set(top, (open.get(top) ?? 1) - 1);\n if (top === name) return;\n }\n };\n const replace = (start: number, end: number, name: string): void => {\n out.push(html.slice(copied, start), BLOCK.has(name) ? \"\\n\" : INLINE.has(name) ? \"\" : \" \");\n copied = end;\n };\n let index = 0;\n while (index < html.length) {\n const lt = html.indexOf(\"<\", index);\n if (lt === -1) break;\n if (html.startsWith(\"<!--\", lt)) {\n const end = html.indexOf(\"-->\", lt + 4);\n index = end === -1 ? html.length : end + 3;\n continue;\n }\n const next = html.charCodeAt(lt + 1);\n if (next === 0x21 || next === 0x3f) {\n const end = html.indexOf(\">\", lt);\n index = end === -1 ? html.length : end + 1;\n continue;\n }\n const closing = next === 0x2f;\n const nameStart = lt + (closing ? 2 : 1);\n if (!isLetter(html.charCodeAt(nameStart))) {\n index = lt + 1;\n continue;\n }\n let nameEnd = nameStart + 1;\n while (nameEnd < html.length && isNameChar(html.charCodeAt(nameEnd))) nameEnd += 1;\n const name = html.slice(nameStart, nameEnd).toLowerCase();\n const tagEnd = endOfTag(html, nameEnd);\n index = tagEnd;\n if (closing) {\n // the deepest elements close first, and those are the ones that were removed\n const removed = extra.get(name) ?? 0;\n if (removed > 0) {\n extra.set(name, removed - 1);\n replace(lt, tagEnd, name);\n } else {\n popTo(name);\n }\n continue;\n }\n if (RAW_TEXT.has(name)) {\n const end = new RegExp(`</${name}[\\\\s/>]`, \"gi\");\n end.lastIndex = tagEnd;\n index = end.exec(html)?.index ?? html.length;\n continue;\n }\n if (VOID.has(name) || html.charCodeAt(tagEnd - 2) === 0x2f) continue;\n if (IMPLIED_END.has(name)) popTo(name);\n if (stack.length >= MAX_DEPTH) {\n extra.set(name, (extra.get(name) ?? 0) + 1);\n dropped += 1;\n replace(lt, tagEnd, name);\n continue;\n }\n stack.push(name);\n open.set(name, (open.get(name) ?? 0) + 1);\n }\n if (dropped === 0) return { html, dropped };\n out.push(html.slice(copied));\n return { html: out.join(\"\"), dropped };\n};\n\nconst attribute = (node: DomNode, name: string): string | null =>\n typeof (node as Partial<DomElement>).getAttribute === \"function\"\n ? (node as DomElement).getAttribute(name)\n : null;\n\n/** A `<template shadowrootmode>` holds a declarative shadow root: the browser shows its content. */\nconst isShadowRoot = (node: DomNode): boolean =>\n attribute(node, \"shadowrootmode\") !== null || attribute(node, \"shadowroot\") !== null;\n\n/** The reader reads only these properties of an inline style. */\nconst STYLE_PROPERTIES = new Set([\"display\", \"font-size\"]);\n\n/**\n * This function reads `display` and `font-size` from the `style` attribute of an element. The last\n * declaration wins, and an `!important` declaration wins over a declaration that is not important.\n * It reads no style sheet. Its time grows in proportion to the length of the attribute.\n */\nconst inlineStyle = (node: DomNode): ReadonlyMap<string, string> => {\n const found = new Map<string, string>();\n const style = attribute(node, \"style\");\n if (style === null || style.length === 0) return found;\n const important = new Set<string>();\n for (const declaration of style.split(\";\")) {\n const colon = declaration.indexOf(\":\");\n if (colon === -1) continue;\n const property = declaration.slice(0, colon).trim().toLowerCase();\n if (!STYLE_PROPERTIES.has(property)) continue;\n let value = declaration\n .slice(colon + 1)\n .trim()\n .toLowerCase();\n const bang = value.lastIndexOf(\"!\");\n const strong = bang !== -1 && value.slice(bang + 1).trim() === \"important\";\n if (strong) value = value.slice(0, bang).trim();\n if (important.has(property) && !strong) continue;\n if (strong) important.add(property);\n found.set(property, value);\n }\n return found;\n};\n\n/**\n * This function tells if a reader of the page does not get an element and its content. A browser\n * does not show an element with `display: none` or with the attribute `hidden` (unless its inline\n * style sets a different display). A screen reader skips an element with `aria-hidden=\"true\"`, and\n * a model reads a page as a screen reader does.\n */\nconst notRendered = (node: DomNode, style: ReadonlyMap<string, string>): boolean => {\n const display = style.get(\"display\");\n if (display === \"none\") return true;\n if (display === undefined && attribute(node, \"hidden\") !== null) return true;\n return attribute(node, \"aria-hidden\")?.trim().toLowerCase() === \"true\";\n};\n\n/** A font size of zero: `0`, `0px`, `0em` or `0%`. */\nconst ZERO_SIZE = /^\\+?(?:0+(?:\\.0*)?|\\.0+)(?:[a-z]{1,4}|%)?$/;\n/** These sizes are a part of the size of the parent, so a part of zero stays zero. */\nconst RELATIVE_SIZE =\n /^(?:\\+?[\\d.]+(?:em|ex|ch|cap|ic|lh|%)|larger|smaller|inherit|unset|revert|revert-layer)$/;\n\n/** This function tells if the text of an element has a zero size. A child that sets a size of its own shows its text again. */\nconst zeroSize = (fontSize: string | undefined, parentZero: boolean): boolean => {\n if (fontSize === undefined) return parentZero;\n if (ZERO_SIZE.test(fontSize)) return true;\n return parentZero && RELATIVE_SIZE.test(fontSize);\n};\n\ninterface Walked {\n readonly text: string;\n readonly elements: number;\n readonly links: number;\n readonly depth: number;\n /** These nodes are not text for a reader: the elements that `notRendered` refuses, and the text of a zero size. */\n readonly unseen: readonly DomNode[];\n}\n\ninterface Frame {\n readonly name: string;\n /** The children of an element are read with `nextSibling`, which costs nothing in linkedom. */\n next: DomNode | null | undefined;\n /** linkedom gives a document type no `nextSibling`, so the few children of a document are read as a list. */\n readonly list: ArrayLike<DomNode> | undefined;\n index: number;\n /** This value is true when the text of the element has a zero size. */\n readonly zero: boolean;\n}\n\n/**\n * This function walks a tree with a stack of its own. Thus the depth of the markup cannot overflow\n * the stack. It reads only the text that a reader gets (refer to `notRendered` and `zeroSize`), and it\n * gives the nodes that it did not read.\n */\nconst walkText = (root: DomNode): Walked => {\n const parts: string[] = [];\n const unseen: DomNode[] = [];\n // a link or a button ends a word even where the markup runs it into the\n // next (\"Offers from AmexGlobal Dining Access\"), though not before a stop\n let seam = false;\n let elements = 0;\n let links = 0;\n let depth = 0;\n const stack: Frame[] = [];\n const enter = (node: DomNode): void => {\n if (node.nodeType === 3) {\n if (stack[stack.length - 1]?.zero === true) {\n unseen.push(node);\n return;\n }\n const text = node.textContent ?? \"\";\n if (seam && /^[^\\s.,;:!?)\\]–—-]/.test(text)) parts.push(\" \");\n if (text.length > 0) seam = false;\n parts.push(text);\n return;\n }\n if (node.nodeType !== 1 && node.nodeType !== 9 && node.nodeType !== 11) return;\n const name = node.nodeName.toLowerCase();\n let container = node;\n if (name === \"template\") {\n if (!isShadowRoot(node)) return;\n container = (node as DomElement).content ?? node;\n } else if (SKIP.has(name)) {\n return;\n }\n let zero = stack[stack.length - 1]?.zero ?? false;\n if (node.nodeType === 1) {\n const style = inlineStyle(node);\n if (notRendered(node, style)) {\n unseen.push(node);\n return;\n }\n zero = zeroSize(style.get(\"font-size\"), zero);\n elements += 1;\n if (name === \"a\") links += 1;\n if (BLOCK.has(name)) parts.push(\"\\n\");\n else if (!INLINE.has(name) && !CELL.has(name)) seam = true;\n }\n stack.push(\n container.nodeType === 1\n ? { name, next: container.firstChild, list: undefined, index: 0, zero }\n : { name, next: undefined, list: container.childNodes, index: 0, zero },\n );\n if (stack.length > depth) depth = stack.length;\n };\n const leave = (name: string): void => {\n const block = BLOCK.has(name);\n if (block) parts.push(\"\\n\");\n else if (CELL.has(name)) parts.push(\" \");\n if (block) seam = false;\n else if (WORD_END.has(name) || !INLINE.has(name)) seam = true;\n };\n enter(root);\n while (stack.length > 0) {\n const frame = stack[stack.length - 1]!;\n let child: DomNode | null | undefined;\n if (frame.list !== undefined) {\n child = frame.index < frame.list.length ? frame.list[frame.index] : undefined;\n frame.index += 1;\n } else {\n child = frame.next;\n if (child !== null && child !== undefined) frame.next = child.nextSibling;\n }\n if (child === null || child === undefined) {\n stack.pop();\n leave(frame.name);\n } else {\n enter(child);\n }\n }\n const text = parts\n .join(\"\")\n .split(\"\\n\")\n .map((line) =>\n line\n .replace(/[-]/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim(),\n )\n .filter((line) => line.length > 0)\n .join(\"\\n\");\n return { text, elements, links, depth, unseen };\n};\n\n/** This function gives the text that a reader sees, with one line for each block. */\nexport const visibleText = (root: DomNode): string => walkText(root).text;\n\n// ── JSON-LD ─────────────────────────────────────────────────────────────────\n\nconst WRAPPERS: ReadonlyArray<readonly [RegExp, RegExp]> = [\n [/^<!--/, /-->$/],\n [/^\\/\\/\\s{0,8}<!\\[CDATA\\[/, /\\/\\/\\s{0,8}\\]\\]>$/],\n [/^\\/\\*\\s{0,8}<!\\[CDATA\\[\\s{0,8}\\*\\//, /\\/\\*\\s{0,8}\\]\\]>\\s{0,8}\\*\\/$/],\n [/^<!\\[CDATA\\[/, /\\]\\]>$/],\n];\n\nconst unwrap = (source: string): string => {\n let text = source.replace(/^/, \"\").trim();\n for (const [open, close] of WRAPPERS) {\n text = text.replace(open, \"\").trim();\n text = text.replace(close, \"\").trim();\n }\n return text;\n};\n\n/**\n * This function cuts a script into its top-level JSON values. It also repairs two common faults: a\n * comma before a closing bracket, and a raw control character inside a string.\n */\nconst jsonValues = (source: string): string[] => {\n const values: string[] = [];\n let out: string[] = [];\n let depth = 0;\n let inString = false;\n let escaped = false;\n let pendingComma = false;\n for (let index = 0; index < source.length; index += 1) {\n const char = source[index]!;\n const code = source.charCodeAt(index);\n if (inString) {\n if (escaped) escaped = false;\n else if (char === \"\\\\\") escaped = true;\n else if (char === '\"') inString = false;\n // a string between two values is skipped with them\n if (depth === 0) continue;\n out.push(\n code >= 0x20\n ? char\n : char === \"\\n\"\n ? \"\\\\n\"\n : char === \"\\r\"\n ? \"\\\\r\"\n : char === \"\\t\"\n ? \"\\\\t\"\n : `\\\\u${code.toString(16).padStart(4, \"0\")}`,\n );\n continue;\n }\n if (depth === 0) {\n // between values only an object or an array starts a value\n if (char === \"{\" || char === \"[\") {\n depth = 1;\n out = [char];\n pendingComma = false;\n } else if (char === '\"') {\n inString = true;\n }\n continue;\n }\n if (code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0d) {\n out.push(char);\n continue;\n }\n if (char === \",\") {\n if (pendingComma) out.push(\",\");\n pendingComma = true;\n continue;\n }\n if (pendingComma) {\n // a comma before a closing bracket is dropped\n if (char !== \"}\" && char !== \"]\") out.push(\",\");\n pendingComma = false;\n }\n out.push(char);\n if (char === '\"') inString = true;\n else if (char === \"{\" || char === \"[\") depth += 1;\n else if (char === \"}\" || char === \"]\") {\n depth -= 1;\n if (depth === 0) {\n values.push(out.join(\"\"));\n out = [];\n }\n }\n }\n // a value that never closes is given whole, and its parse fails\n if (depth > 0) values.push(out.join(\"\"));\n return values;\n};\n\nconst parseJsonLd = (\n sources: readonly string[],\n): { readonly records: Record<string, unknown>[]; readonly unreadable: number } => {\n const records: Record<string, unknown>[] = [];\n let unreadable = 0;\n // an explicit stack: JSON nested deeply in arrays cannot overflow the call stack\n const push = (first: unknown): void => {\n const stack: unknown[] = [first];\n const found: Record<string, unknown>[] = [];\n while (stack.length > 0) {\n const value = stack.pop();\n if (Array.isArray(value)) {\n for (let index = value.length - 1; index >= 0; index -= 1) stack.push(value[index]);\n continue;\n }\n if (value === null || typeof value !== \"object\") continue;\n const record = value as Record<string, unknown>;\n const graph = record[\"@graph\"];\n if (graph !== undefined && graph !== null && typeof graph === \"object\") {\n const { \"@graph\": _graph, ...rest } = record;\n // the record itself first, then its graph, in the order of the page\n stack.push(graph);\n if (Object.keys(rest).some((key) => key !== \"@context\")) found.push(rest);\n continue;\n }\n found.push(record);\n }\n records.push(...found);\n };\n for (const source of sources) {\n const text = unwrap(source);\n if (text.length === 0) continue;\n try {\n push(JSON.parse(text));\n continue;\n } catch {\n // repaired below\n }\n let failed = false;\n let any = false;\n for (const value of jsonValues(text)) {\n try {\n push(JSON.parse(value));\n any = true;\n } catch {\n failed = true;\n }\n }\n if (failed || !any) unreadable += 1;\n }\n return { records, unreadable };\n};\n\n/**\n * This function reads the JSON-LD blocks and makes arrays and `@graph` flat. It repairs a comma\n * before a closing bracket, a raw line break in a string and two values in one block. It ignores\n * a block that it cannot repair.\n */\nexport const readJsonLd = (sources: readonly string[]): Record<string, unknown>[] =>\n parseJsonLd(sources).records;\n\n/** This function gives the types that a JSON-LD object declares, without the schema.org prefix. */\nexport const typesOf = (record: Record<string, unknown>): string[] => {\n const raw = record[\"@type\"];\n const list = Array.isArray(raw) ? raw : raw === undefined ? [] : [raw];\n return list\n .filter((value): value is string => typeof value === \"string\")\n .map((value) => value.replace(/^.*[/#]/, \"\").replace(/^[A-Za-z][\\w.+-]*:/, \"\"));\n};\n\n// ── URLs ────────────────────────────────────────────────────────────────────\n\nexport { canonicalUrl };\n\nconst isWeb = (url: URL): boolean => url.protocol === \"http:\" || url.protocol === \"https:\";\nconst siteOf = (host: string): string => host.toLowerCase().replace(/^www\\./, \"\");\n\n// ── what the page needs ─────────────────────────────────────────────────────\n\n/** The marks of the pages that a bot check or a firewall sends in place of the content. */\nconst CHALLENGES: ReadonlyArray<readonly [string, RegExp]> = [\n [\"Incapsula\", /_Incapsula_Resource|Incapsula incident ID/i],\n [\"Cloudflare\", /\\/cdn-cgi\\/challenge-platform\\/|cf-chl-|cf_chl_opt|cf-browser-verification/i],\n [\"Sucuri\", /sucuri_cloudproxy|Sucuri WebSite Firewall/i],\n [\"DataDome\", /captcha-delivery\\.com/i],\n [\"PerimeterX\", /_pxCaptcha|px-captcha|captcha\\.px-cdn\\.net/i],\n [\"AWS WAF\", /AwsWafIntegration|token\\.awswaf\\.com|\\/challenge\\.js.{0,80}awswaf/i],\n [\"DDoS-Guard\", /ddos-guard\\.net\\/|__ddg1_/i],\n];\nconst CHALLENGE_TITLE =\n /^(?:just a moment\\.*|attention required!? \\| cloudflare|access to this page has been denied\\.?|please verify you are a human|are you a robot\\??|ddos-guard|checking your browser.{0,40})$/i;\n\nconst ASKS_FOR_SCRIPT =\n /(?:enable|turn on|activate|switch on|allow)\\s+(?:your\\s+)?javascript|javascript\\s+(?:is\\s+)?(?:required|disabled|turned off|not enabled|must be enabled|needs to be enabled)|(?:requires?|needs?)\\s+javascript|javascript\\s+aktivieren|activ(?:er|ez)\\s+(?:le\\s+)?javascript|habilit(?:ar|e)\\s+(?:el\\s+)?javascript/i;\n/** The noscript of a comment widget (Disqus) asks for JavaScript for the comments, not for the page. */\nconst COMMENTS = /comments|disqus/i;\n\n/** These elements hold the whole page of an application that its scripts draw. */\nconst APP_ROOTS = \"#root, #app, #__next, #__nuxt, #___gatsby, app-root, my-app, [data-reactroot]\";\n\nconst EMBEDDED: ReadonlyArray<readonly [string, (html: string, doc: DomDocument) => boolean]> = [\n [\"next\", (_html, doc) => doc.querySelector('script[id=\"__NEXT_DATA__\"]') !== null],\n [\"next-app\", (html) => html.includes(\"self.__next_f.push\")],\n [\"nuxt\", (html) => html.includes(\"__NUXT_DATA__\") || html.includes(\"window.__NUXT__\")],\n [\"apollo\", (html) => html.includes(\"__APOLLO_STATE__\")],\n];\n\n/** This function reads `content=\"0; url=/home\"`. A refresh with no URL reloads the page, and is no redirect. */\nconst refreshTarget = (\n content: string,\n): { readonly delay: number; readonly url: string } | undefined => {\n const text = content.slice(0, 2000).trim();\n const delay = /^\\d+(?:\\.\\d*)?/.exec(text)?.[0] ?? \"\";\n const url = text\n .slice(delay.length)\n .replace(/^[\\s;,]+/, \"\")\n .replace(/^url\\s*=\\s*/i, \"\")\n .replace(/^([\"'])(.*)\\1$/s, \"$2\")\n .trim();\n return url.length === 0 ? undefined : { delay: Number(delay || 0), url };\n};\n\n// ── composing the text ──────────────────────────────────────────────────────\n\n/** This function cuts text to a length at the end of a line where it can. It does not cut a surrogate pair. */\nconst cutHead = (text: string, length: number): string => {\n if (text.length <= length) return text;\n if (length <= 0) return \"\";\n const line = text.lastIndexOf(\"\\n\", length);\n let end = line > length - 2000 && line > 0 ? line : length;\n const code = text.charCodeAt(end - 1);\n if (code >= 0xd800 && code <= 0xdbff) end -= 1;\n return text.slice(0, end);\n};\n\n/** This function keeps the end of the text, from the start of a line where it can. */\nconst cutTail = (text: string, length: number): string => {\n if (text.length <= length) return text;\n if (length <= 0) return \"\";\n let start = text.length - length;\n const line = text.indexOf(\"\\n\", start);\n if (line !== -1 && line < start + 2000) start = line + 1;\n const code = text.charCodeAt(start);\n if (code >= 0xdc00 && code <= 0xdfff) start += 1;\n return text.slice(start);\n};\n\n/** This function keeps the start and the end of a text that is too long: a footer keeps its hours. */\nconst capText = (text: string): string =>\n text.length <= MAX_TEXT_CHARS\n ? text\n : `${cutHead(text, MAX_TEXT_CHARS - REST_RESERVE_CHARS - 2)}\\n\\n${cutTail(text, REST_RESERVE_CHARS)}`;\n\n/** A line that can label the lines below it: short, with no number, and not a sentence (\"Opening hours\"). */\nconst isHeading = (line: string): boolean =>\n line.length <= 40 && !/[.!?]$/.test(line) && !/[0-9]/.test(line);\n\n/**\n * This function finds the lines of the article in the lines of the page, one for one and in order.\n * Thus a line that the article and a sidebar share stays in the sidebar (\"10 am – 5 pm\").\n */\nconst articleLines = (all: readonly string[], main: readonly string[]): Uint8Array => {\n const taken = new Uint8Array(all.length);\n const positions = new Map<string, number[]>();\n all.forEach((line, index) => {\n const list = positions.get(line);\n if (list === undefined) positions.set(line, [index]);\n else list.push(index);\n });\n const cursors = new Map<string, number>();\n let at = 0;\n let matched = 0;\n for (const line of main) {\n const list = positions.get(line);\n if (list === undefined) continue;\n let cursor = cursors.get(line) ?? 0;\n while (cursor < list.length && list[cursor]! < at) cursor += 1;\n cursors.set(line, cursor);\n const position = list[cursor];\n if (position === undefined) continue;\n // a line far ahead is the same line only when the page has it once\n if (matched > 0 && position - at > 400 && list.length > 1) continue;\n taken[position] = 1;\n cursors.set(line, cursor + 1);\n at = position + 1;\n matched += 1;\n }\n return taken;\n};\n\n/** This function gives the lines of the page that are not in the article. Each run keeps the heading above it. */\nconst restOf = (all: readonly string[], taken: Uint8Array): string[] => {\n const rest: string[] = [];\n for (let index = 0; index < all.length; index += 1) {\n if (taken[index] === 1) continue;\n const above = all[index - 1];\n if (index > 0 && taken[index - 1] === 1 && above !== undefined && isHeading(above))\n rest.push(above);\n rest.push(all[index]!);\n }\n return rest;\n};\n\nexport const extractPage = (html: string, pageUrl: string): PageExtract => {\n const notes: string[] = [];\n const flat = flattenDeepMarkup(html);\n if (flat.dropped > 0) {\n notes.push(\n `the markup nests deeper than ${String(MAX_DEPTH)} elements, so the reader read the deeper tags as text`,\n );\n }\n const doc = parse(flat.html);\n const meta = (selector: string): string | null =>\n doc.querySelector(selector)?.getAttribute(\"content\")?.trim() || null;\n\n const scripts = [...doc.querySelectorAll(\"script\")];\n const parsed = parseJsonLd(\n scripts\n .filter((node) =>\n /^\\s*application\\/ld\\+json\\s*(?:;|$)/i.test(node.getAttribute(\"type\") ?? \"\"),\n )\n .map((node) => node.textContent ?? \"\"),\n );\n const jsonld = parsed.records;\n if (parsed.unreadable > 0) {\n notes.push(\n `${String(parsed.unreadable)} JSON-LD ${parsed.unreadable === 1 ? \"block\" : \"blocks\"} could not be read`,\n );\n }\n\n const page = (() => {\n try {\n return new URL(pageUrl);\n } catch {\n return undefined;\n }\n })();\n const baseHref = doc.querySelector(\"base[href]\")?.getAttribute(\"href\");\n const base = (() => {\n try {\n const resolved = new URL(baseHref ?? pageUrl, pageUrl);\n // a <base href=\"javascript:...\"> would take every relative link with it\n return isWeb(resolved) ? resolved.href : pageUrl;\n } catch {\n return pageUrl;\n }\n })();\n const resolve = (raw: string): URL | undefined => {\n try {\n const url = new URL(raw.trim(), base);\n if (!isWeb(url)) return undefined;\n url.username = \"\";\n url.password = \"\";\n return url;\n } catch {\n return undefined;\n }\n };\n const self = page === undefined ? undefined : canonicalUrl(page.href);\n\n const refresh = [...doc.querySelectorAll(\"meta[http-equiv]\")]\n .filter((node) => /^\\s*refresh\\s*$/i.test(node.getAttribute(\"http-equiv\") ?? \"\"))\n .map((node) => refreshTarget(node.getAttribute(\"content\") ?? \"\"))\n .find((target) => target !== undefined);\n const refreshUrl = refresh === undefined ? undefined : resolve(refresh.url);\n const redirect =\n refresh !== undefined &&\n refresh.delay <= 5 &&\n refreshUrl !== undefined &&\n canonicalUrl(refreshUrl.href) !== self\n ? canonicalUrl(refreshUrl.href)\n : null;\n\n const canonical = (() => {\n const node = [...doc.querySelectorAll(\"link[rel][href]\")].find((link) =>\n (link.getAttribute(\"rel\") ?? \"\").toLowerCase().split(/\\s+/).includes(\"canonical\"),\n );\n const url = node === undefined ? undefined : resolve(node.getAttribute(\"href\") ?? \"\");\n // a canonical on another site could take the page's identity: it is not trusted\n if (url === undefined || page === undefined || siteOf(url.hostname) !== siteOf(page.hostname)) {\n return null;\n }\n return canonicalUrl(url.href);\n })();\n\n const clean = (value: string | null | undefined): string =>\n (value ?? \"\").replace(/\\s+/g, \" \").trim();\n const titles = [...doc.querySelectorAll(\"title\")];\n // an <svg> keeps a <title> of its own (\"Icon\"), which names no page\n const inSvg = (node: DomNode): boolean => {\n for (let parent = node.parentNode; parent; parent = parent.parentNode) {\n if (parent.nodeName.toLowerCase() === \"svg\") return true;\n }\n return false;\n };\n const title =\n meta('meta[property=\"og:title\"]') ||\n clean(titles.find((node) => !inSvg(node))?.textContent) ||\n clean(doc.querySelector(\"h1\")?.textContent) ||\n pageUrl;\n const description = meta('meta[name=\"description\"]') ?? meta('meta[property=\"og:description\"]');\n const lang = doc.documentElement?.getAttribute(\"lang\")?.trim() || null;\n const noscript = [...doc.querySelectorAll(\"noscript\")]\n .map((node) => node.textContent ?? \"\")\n .filter((text) => !COMMENTS.test(text))\n .join(\" \");\n const embedded = EMBEDDED.filter(([, found]) => found(html, doc)).map(([name]) => name);\n const appShell = [...doc.querySelectorAll(APP_ROOTS)].some(\n (node) => (node.textContent ?? \"\").trim().length === 0,\n );\n\n // the whole document: a page that omits <html> is not under\n // documentElement at all, and document.body can be a decoy (see DomDocument;\n // a homepage read as 0 characters and went to the browser for nothing)\n const walked = walkText(doc);\n // the links and Readability read only what a reader gets: text that the page hides goes now\n for (const node of walked.unseen) node.remove?.();\n\n const seen = new Set<string>(self === undefined ? [] : [self]);\n const links: PageLink[] = [];\n if (redirect !== null) {\n seen.add(redirect);\n links.push({ href: redirect, text: \"\" });\n }\n for (const anchor of doc.querySelectorAll(\"a[href]\")) {\n if (links.length >= MAX_LINKS) break;\n const url = resolve(anchor.getAttribute(\"href\") ?? \"\");\n if (url === undefined) continue;\n const href = canonicalUrl(url.href);\n const text =\n (anchor.textContent ?? \"\").replace(/\\s+/g, \" \").trim() ||\n anchor.getAttribute(\"aria-label\")?.trim() ||\n anchor.getAttribute(\"title\")?.trim() ||\n anchor.querySelector(\"img[alt]\")?.getAttribute(\"alt\")?.trim() ||\n \"\";\n // the link to the target of a meta refresh gets the text of the first anchor that names it\n if (href === redirect && links[0]?.href === redirect && links[0].text === \"\") {\n links[0] = { href, text: text.slice(0, 120) };\n continue;\n }\n // a link to the page itself (\"#menu\") is no link\n if (seen.has(href)) continue;\n seen.add(href);\n links.push({ href, text: text.slice(0, 120) });\n }\n\n const all = walked.text;\n let text = capText(all);\n let cut = text.length < all.length;\n if (all.length > SHORT_PAGE_CHARS) {\n const main = (() => {\n if (\n walked.elements > READABILITY_MAX_ELEMENTS ||\n walked.links > READABILITY_MAX_LINKS ||\n walked.depth > READABILITY_MAX_DEPTH\n ) {\n notes.push(\"the page is too large for Readability, so its text is in page order\");\n return \"\";\n }\n try {\n // Readability rewrites the document, and the reader has no more use for it. It gives its\n // article as an element, read here so that blocks stay lines\n const article = new Readability<DomNode>(\n doc as unknown as ConstructorParameters<typeof Readability>[0],\n {\n charThreshold: 500,\n disableJSONLD: true,\n serializer: (node) => node as unknown as DomNode,\n },\n ).parse();\n return article?.content === null || article?.content === undefined\n ? \"\"\n : visibleText(article.content);\n } catch {\n notes.push(\"Readability could not read the page, so its text is in page order\");\n return \"\";\n }\n })();\n if (main.length > 500) {\n // the article first, then every other line the page shows: a venue's\n // hours sit in a table or a sidebar Readability drops (a museum's visit\n // page), not only in its footer\n const lines = all.split(\"\\n\");\n const rest = restOf(lines, articleLines(lines, main.split(\"\\n\"))).join(\"\\n\");\n const head = cutHead(main, MAX_TEXT_CHARS - Math.min(rest.length, REST_RESERVE_CHARS) - 2);\n const tail = cutHead(rest, MAX_TEXT_CHARS - head.length - 2);\n text = rest.length > 0 ? `${head}\\n\\n${tail}` : head;\n cut = head.length < main.length || tail.length < rest.length;\n }\n }\n if (cut) notes.push(`the text is cut to ${String(MAX_TEXT_CHARS)} characters`);\n\n const visibleChars = all.length;\n const pageTitle = clean(titles[0]?.textContent);\n const blocked =\n visibleChars < 2000\n ? CHALLENGE_TITLE.test(pageTitle)\n ? (CHALLENGES.find(([, pattern]) => pattern.test(html))?.[0] ?? \"a bot check\")\n : (CHALLENGES.find(([, pattern]) => pattern.test(html))?.[0] ?? null)\n : null;\n const reason =\n // a browser would only go past a bot check or follow the redirect\n blocked !== null || redirect !== null\n ? null\n : // an app shell shows next to nothing; a small venue page can still say\n // its hours and address in a hundred characters, and needs no browser\n visibleChars < 80\n ? `only ${String(visibleChars)} characters of visible text`\n : visibleChars < 1200 && ASKS_FOR_SCRIPT.test(noscript)\n ? \"the page asks for JavaScript\"\n : visibleChars < 1200 && appShell\n ? \"an empty application root\"\n : null;\n\n return {\n title: title.replace(/\\s+/g, \" \").slice(0, 300),\n description,\n text,\n ...(cut ? { fullText: all } : {}),\n jsonld,\n links,\n lang,\n visibleChars,\n embedded,\n needsRender: reason !== null,\n renderReason: reason,\n canonical,\n redirect,\n blocked,\n notes,\n };\n};\n"],"mappings":"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ACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAED,MAAM,wBAAQ,IAAI,IAAI;CACpB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;AAKD,MAAM,yBAAS,IAAI,IAAI;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAED,MAAM,uBAAO,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC;AACjC,MAAM,2BAAW,IAAI,IAAI,CAAC,KAAK,QAAQ,CAAC;;AAGxC,MAAM,mBAAmB;AACzB,MAAM,iBAAiB;;AAEvB,MAAM,qBAAqB;AAC3B,MAAM,YAAY;;AAElB,MAAM,2BAA2B;AACjC,MAAM,wBAAwB;AAC9B,MAAM,wBAAwB;;;;;AAK9B,MAAM,YAAY;AAgClB,MAAM,SAAS,SACZ,UAAU,IAAI,CAAC,CAA0C;;AAG5D,MAAM,uBAAO,IAAI,IAAI;CACnB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAED,MAAM,2BAAW,IAAI,IAAI;CACvB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAED,MAAM,8BAAc,IAAI,IAAI;CAC1B;CACA;CACA;CACA;CACA;CACA;CAC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"}
|