webforai 4.2.0 → 4.3.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/dist/adapters/sites/youtube.js +4 -2
- package/dist/bin.js +662 -474
- package/dist/cjs/adapters/sites/youtube.js +5 -2
- package/dist/cjs/detect-client-shell.js +2 -0
- package/dist/cjs/extractors/lib/terminators.js +2 -0
- package/dist/cjs/index.js +3 -0
- package/dist/cjs/mdast-handlers/custom-code-handler.js +31 -2
- package/dist/cjs/normalize/index.js +97 -4
- package/dist/cjs/utils/detect-code-lang.js +4 -5
- package/dist/cjs/utils/parse-html.js +73 -5
- package/dist/detect-client-shell.js +1 -0
- package/dist/extractors/lib/terminators.js +2 -0
- package/dist/index.js +2 -0
- package/dist/mdast-handlers/custom-code-handler.js +31 -2
- package/dist/normalize/index.js +94 -3
- package/dist/types/adapters/sites/youtube.d.ts +2 -0
- package/dist/types/adapters/sites/youtube.js.map +1 -1
- package/dist/types/detect-client-shell.d.ts +2 -0
- package/dist/types/detect-client-shell.js.map +1 -1
- package/dist/types/extractors/lib/terminators.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/mdast-handlers/custom-code-handler.d.ts +5 -0
- package/dist/types/mdast-handlers/custom-code-handler.js.map +1 -1
- package/dist/types/normalize/index.d.ts +21 -0
- package/dist/types/normalize/index.js.map +1 -1
- package/dist/types/utils/detect-code-lang.d.ts +7 -1
- package/dist/types/utils/detect-code-lang.js.map +1 -1
- package/dist/types/utils/parse-html.d.ts +12 -6
- package/dist/types/utils/parse-html.js.map +1 -1
- package/dist/utils/detect-code-lang.js +4 -5
- package/dist/utils/parse-html.js +73 -5
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -6,7 +6,7 @@ import { CommanderError, program } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "webforai",
|
|
9
|
-
version: "4.
|
|
9
|
+
version: "4.3.0",
|
|
10
10
|
description: "Convert web pages and local HTML to clean, LLM-ready Markdown: library, CLI (npx webforai) and hosted-API client.",
|
|
11
11
|
author: "inaridiy",
|
|
12
12
|
license: "Apache-2.0",
|
|
@@ -1360,8 +1360,440 @@ import path5 from "node:path";
|
|
|
1360
1360
|
// src/utils/parse-html.ts
|
|
1361
1361
|
import { fromParse5 } from "hast-util-from-parse5";
|
|
1362
1362
|
import { parse, parseFragment } from "parse5";
|
|
1363
|
+
|
|
1364
|
+
// src/utils/hast-fast.ts
|
|
1365
|
+
var EMPTY_METRICS = { text: 0, link: 0, commas: 0, paragraphs: 0, elements: 0 };
|
|
1366
|
+
var COMMA_REGEX = /[,、,،]/g;
|
|
1367
|
+
var PARAGRAPH_LIKE = /* @__PURE__ */ new Set(["p", "pre", "li", "blockquote", "figcaption", "dd", "dt"]);
|
|
1368
|
+
var NON_PROSE_TAGS = /* @__PURE__ */ new Set(["script", "style", "noscript", "template", "svg", "math", "iframe", "object"]);
|
|
1369
|
+
var isElement = (node) => typeof node === "object" && node !== null && node.type === "element";
|
|
1370
|
+
var isParent = (node) => typeof node === "object" && node !== null && Array.isArray(node.children);
|
|
1371
|
+
var classList = (element4) => {
|
|
1372
|
+
const className = element4.properties?.className;
|
|
1373
|
+
if (Array.isArray(className)) {
|
|
1374
|
+
return className;
|
|
1375
|
+
}
|
|
1376
|
+
if (typeof className === "string") {
|
|
1377
|
+
return className.split(/\s+/).filter(Boolean);
|
|
1378
|
+
}
|
|
1379
|
+
return [];
|
|
1380
|
+
};
|
|
1381
|
+
var matchString = (element4) => {
|
|
1382
|
+
const id = element4.properties?.id;
|
|
1383
|
+
const classes = classList(element4);
|
|
1384
|
+
if (!id && classes.length === 0) {
|
|
1385
|
+
return element4.tagName;
|
|
1386
|
+
}
|
|
1387
|
+
return `${element4.tagName} ${typeof id === "string" ? id : ""} ${classes.join(" ")}`;
|
|
1388
|
+
};
|
|
1389
|
+
var MetricsCollector = class {
|
|
1390
|
+
#cache = /* @__PURE__ */ new WeakMap();
|
|
1391
|
+
metrics(node) {
|
|
1392
|
+
if (node.type === "text") {
|
|
1393
|
+
const value = node.value;
|
|
1394
|
+
if (value.trim().length === 0) {
|
|
1395
|
+
return EMPTY_METRICS;
|
|
1396
|
+
}
|
|
1397
|
+
return {
|
|
1398
|
+
text: value.length,
|
|
1399
|
+
link: 0,
|
|
1400
|
+
commas: (value.match(COMMA_REGEX) ?? []).length,
|
|
1401
|
+
paragraphs: 0,
|
|
1402
|
+
elements: 0
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
if (!isParent(node)) {
|
|
1406
|
+
return EMPTY_METRICS;
|
|
1407
|
+
}
|
|
1408
|
+
const cached = this.#cache.get(node);
|
|
1409
|
+
if (cached) {
|
|
1410
|
+
return cached;
|
|
1411
|
+
}
|
|
1412
|
+
if (isElement(node) && NON_PROSE_TAGS.has(node.tagName)) {
|
|
1413
|
+
this.#cache.set(node, EMPTY_METRICS);
|
|
1414
|
+
return EMPTY_METRICS;
|
|
1415
|
+
}
|
|
1416
|
+
let text7 = 0;
|
|
1417
|
+
let link = 0;
|
|
1418
|
+
let commas = 0;
|
|
1419
|
+
let paragraphs = 0;
|
|
1420
|
+
let elements = 0;
|
|
1421
|
+
for (const child of node.children) {
|
|
1422
|
+
const childMetrics = this.metrics(child);
|
|
1423
|
+
text7 += childMetrics.text;
|
|
1424
|
+
link += childMetrics.link;
|
|
1425
|
+
commas += childMetrics.commas;
|
|
1426
|
+
paragraphs += childMetrics.paragraphs;
|
|
1427
|
+
elements += childMetrics.elements;
|
|
1428
|
+
if (isElement(child)) {
|
|
1429
|
+
elements += 1;
|
|
1430
|
+
if (PARAGRAPH_LIKE.has(child.tagName)) {
|
|
1431
|
+
paragraphs += 1;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
if (isElement(node) && node.tagName === "a") {
|
|
1436
|
+
link = text7;
|
|
1437
|
+
}
|
|
1438
|
+
const metrics = { text: text7, link, commas, paragraphs, elements };
|
|
1439
|
+
this.#cache.set(node, metrics);
|
|
1440
|
+
return metrics;
|
|
1441
|
+
}
|
|
1442
|
+
textLength(node) {
|
|
1443
|
+
return this.metrics(node).text;
|
|
1444
|
+
}
|
|
1445
|
+
/** Fraction of a subtree's text that is link anchor text. `0` for empty subtrees. */
|
|
1446
|
+
linkDensity(node) {
|
|
1447
|
+
const { text: text7, link } = this.metrics(node);
|
|
1448
|
+
if (text7 === 0) {
|
|
1449
|
+
return 0;
|
|
1450
|
+
}
|
|
1451
|
+
return Math.min(1, link / text7);
|
|
1452
|
+
}
|
|
1453
|
+
};
|
|
1454
|
+
var pruneInPlace = (tree, keep) => {
|
|
1455
|
+
if (!isParent(tree)) {
|
|
1456
|
+
return tree;
|
|
1457
|
+
}
|
|
1458
|
+
const stack = [tree];
|
|
1459
|
+
while (stack.length > 0) {
|
|
1460
|
+
const parent = stack.pop();
|
|
1461
|
+
const children = parent.children;
|
|
1462
|
+
let write = 0;
|
|
1463
|
+
for (let read = 0; read < children.length; read++) {
|
|
1464
|
+
const child = children[read];
|
|
1465
|
+
if (!keep(child, parent)) {
|
|
1466
|
+
continue;
|
|
1467
|
+
}
|
|
1468
|
+
children[write++] = child;
|
|
1469
|
+
if (isParent(child)) {
|
|
1470
|
+
stack.push(child);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
children.length = write;
|
|
1474
|
+
}
|
|
1475
|
+
return tree;
|
|
1476
|
+
};
|
|
1477
|
+
var walk = (tree, visit) => {
|
|
1478
|
+
const stack = [{ node: tree, parent: void 0 }];
|
|
1479
|
+
while (stack.length > 0) {
|
|
1480
|
+
const { node, parent } = stack.pop();
|
|
1481
|
+
if (visit(node, parent) === false) {
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1484
|
+
if (isParent(node)) {
|
|
1485
|
+
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
1486
|
+
stack.push({ node: node.children[i], parent: node });
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
var collectElements = (tree, predicate) => {
|
|
1492
|
+
const found = [];
|
|
1493
|
+
walk(tree, (node) => {
|
|
1494
|
+
if (isElement(node) && predicate(node)) {
|
|
1495
|
+
found.push(node);
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
return found;
|
|
1499
|
+
};
|
|
1500
|
+
var findElement = (tree, predicate) => {
|
|
1501
|
+
let found;
|
|
1502
|
+
walk(tree, (node) => {
|
|
1503
|
+
if (found) {
|
|
1504
|
+
return false;
|
|
1505
|
+
}
|
|
1506
|
+
if (isElement(node) && predicate(node)) {
|
|
1507
|
+
found = node;
|
|
1508
|
+
return false;
|
|
1509
|
+
}
|
|
1510
|
+
});
|
|
1511
|
+
return found;
|
|
1512
|
+
};
|
|
1513
|
+
var cloneHast = (tree) => {
|
|
1514
|
+
try {
|
|
1515
|
+
return structuredClone(tree);
|
|
1516
|
+
} catch {
|
|
1517
|
+
return manualClone(tree);
|
|
1518
|
+
}
|
|
1519
|
+
};
|
|
1520
|
+
var manualClone = (node) => {
|
|
1521
|
+
if (Array.isArray(node)) {
|
|
1522
|
+
return node.map(manualClone);
|
|
1523
|
+
}
|
|
1524
|
+
if (typeof node !== "object" || node === null) {
|
|
1525
|
+
return node;
|
|
1526
|
+
}
|
|
1527
|
+
const copy = {};
|
|
1528
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1529
|
+
copy[key] = manualClone(value);
|
|
1530
|
+
}
|
|
1531
|
+
return copy;
|
|
1532
|
+
};
|
|
1533
|
+
var property = (element4, key) => {
|
|
1534
|
+
const properties = element4.properties;
|
|
1535
|
+
if (!properties) {
|
|
1536
|
+
return void 0;
|
|
1537
|
+
}
|
|
1538
|
+
const direct = properties[key];
|
|
1539
|
+
if (direct !== void 0) {
|
|
1540
|
+
return direct;
|
|
1541
|
+
}
|
|
1542
|
+
if (!(key.includes("-") || key.includes(":"))) {
|
|
1543
|
+
return void 0;
|
|
1544
|
+
}
|
|
1545
|
+
return properties[camelCaseAttribute(key)];
|
|
1546
|
+
};
|
|
1547
|
+
var camelCaseAttribute = (key) => key.replace(/[-:]([a-z])/g, (_, character) => character.toUpperCase());
|
|
1548
|
+
var numericProperty = (element4, key) => {
|
|
1549
|
+
const raw = property(element4, key);
|
|
1550
|
+
if (typeof raw === "number") {
|
|
1551
|
+
return Number.isFinite(raw) ? raw : void 0;
|
|
1552
|
+
}
|
|
1553
|
+
if (typeof raw === "string" && raw.length > 0) {
|
|
1554
|
+
const parsed = Number.parseFloat(raw);
|
|
1555
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
1556
|
+
}
|
|
1557
|
+
return void 0;
|
|
1558
|
+
};
|
|
1559
|
+
var stringProperty = (element4, key) => {
|
|
1560
|
+
const raw = property(element4, key);
|
|
1561
|
+
if (typeof raw === "string") {
|
|
1562
|
+
return raw;
|
|
1563
|
+
}
|
|
1564
|
+
if (Array.isArray(raw)) {
|
|
1565
|
+
return raw.join(" ");
|
|
1566
|
+
}
|
|
1567
|
+
if (raw === true) {
|
|
1568
|
+
return "";
|
|
1569
|
+
}
|
|
1570
|
+
return void 0;
|
|
1571
|
+
};
|
|
1572
|
+
var isTruthyAttribute = (element4, key) => {
|
|
1573
|
+
const raw = property(element4, key);
|
|
1574
|
+
if (raw === void 0 || raw === null || raw === false) {
|
|
1575
|
+
return false;
|
|
1576
|
+
}
|
|
1577
|
+
if (raw === true) {
|
|
1578
|
+
return true;
|
|
1579
|
+
}
|
|
1580
|
+
if (typeof raw === "string") {
|
|
1581
|
+
return raw.toLowerCase() !== "false";
|
|
1582
|
+
}
|
|
1583
|
+
return true;
|
|
1584
|
+
};
|
|
1585
|
+
|
|
1586
|
+
// src/adapters/sites/youtube.ts
|
|
1587
|
+
var findObjectEnd = (source, start) => {
|
|
1588
|
+
let depth = 0;
|
|
1589
|
+
let inString = false;
|
|
1590
|
+
let escaped = false;
|
|
1591
|
+
for (let index = start; index < source.length; index++) {
|
|
1592
|
+
const character = source[index];
|
|
1593
|
+
if (escaped) {
|
|
1594
|
+
escaped = false;
|
|
1595
|
+
} else if (character === "\\") {
|
|
1596
|
+
escaped = true;
|
|
1597
|
+
} else if (character === '"') {
|
|
1598
|
+
inString = !inString;
|
|
1599
|
+
} else if (!inString) {
|
|
1600
|
+
if (character === "{") {
|
|
1601
|
+
depth += 1;
|
|
1602
|
+
} else if (character === "}" && --depth === 0) {
|
|
1603
|
+
return index + 1;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
return -1;
|
|
1608
|
+
};
|
|
1609
|
+
var extractAssignedJson = (source, name) => {
|
|
1610
|
+
const markerIndex = source.indexOf(`${name} =`);
|
|
1611
|
+
if (markerIndex === -1) {
|
|
1612
|
+
return void 0;
|
|
1613
|
+
}
|
|
1614
|
+
const start = source.indexOf("{", markerIndex);
|
|
1615
|
+
if (start === -1) {
|
|
1616
|
+
return void 0;
|
|
1617
|
+
}
|
|
1618
|
+
const end = findObjectEnd(source, start);
|
|
1619
|
+
if (end === -1) {
|
|
1620
|
+
return void 0;
|
|
1621
|
+
}
|
|
1622
|
+
try {
|
|
1623
|
+
return JSON.parse(source.slice(start, end));
|
|
1624
|
+
} catch {
|
|
1625
|
+
return void 0;
|
|
1626
|
+
}
|
|
1627
|
+
};
|
|
1628
|
+
var scriptText = (root) => collectElements(root, (element4) => element4.tagName === "script").map(
|
|
1629
|
+
(script) => script.children.map((child) => child.type === "text" ? child.value : "").join("")
|
|
1630
|
+
);
|
|
1631
|
+
var PLAYER_RESPONSE_VARIABLE = "ytInitialPlayerResponse";
|
|
1632
|
+
var findPlayerResponse = (root) => {
|
|
1633
|
+
for (const source of scriptText(root)) {
|
|
1634
|
+
if (!source.includes(PLAYER_RESPONSE_VARIABLE)) {
|
|
1635
|
+
continue;
|
|
1636
|
+
}
|
|
1637
|
+
const parsed = extractAssignedJson(source, PLAYER_RESPONSE_VARIABLE);
|
|
1638
|
+
if (parsed && typeof parsed === "object") {
|
|
1639
|
+
return parsed;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
return void 0;
|
|
1643
|
+
};
|
|
1644
|
+
var youtubeCaptionTracks = (root) => {
|
|
1645
|
+
const player = findPlayerResponse(root);
|
|
1646
|
+
const renderer = player?.captions?.playerCaptionsTracklistRenderer;
|
|
1647
|
+
const tracks = renderer?.captionTracks;
|
|
1648
|
+
if (!Array.isArray(tracks)) {
|
|
1649
|
+
return [];
|
|
1650
|
+
}
|
|
1651
|
+
return tracks.flatMap((track) => {
|
|
1652
|
+
const url = typeof track.baseUrl === "string" ? track.baseUrl : void 0;
|
|
1653
|
+
const languageCode = typeof track.languageCode === "string" ? track.languageCode : void 0;
|
|
1654
|
+
if (!(url && languageCode)) {
|
|
1655
|
+
return [];
|
|
1656
|
+
}
|
|
1657
|
+
const name = track.name?.simpleText;
|
|
1658
|
+
return [
|
|
1659
|
+
{
|
|
1660
|
+
url,
|
|
1661
|
+
languageCode,
|
|
1662
|
+
name: typeof name === "string" ? name : void 0,
|
|
1663
|
+
isAutomatic: track.kind === "asr"
|
|
1664
|
+
}
|
|
1665
|
+
];
|
|
1666
|
+
});
|
|
1667
|
+
};
|
|
1668
|
+
var text3 = (value) => ({ type: "text", value });
|
|
1669
|
+
var element = (tagName, children) => ({
|
|
1670
|
+
type: "element",
|
|
1671
|
+
tagName,
|
|
1672
|
+
properties: {},
|
|
1673
|
+
children
|
|
1674
|
+
});
|
|
1675
|
+
var heading = (depth, value) => element(`h${depth}`, [text3(value)]);
|
|
1676
|
+
var paragraph = (value) => element("p", [text3(value)]);
|
|
1677
|
+
var descriptionNodes = (description) => description.split(/\n{2,}/).map((block) => block.trim()).filter((block) => block.length > 0).map(
|
|
1678
|
+
(block) => element(
|
|
1679
|
+
"p",
|
|
1680
|
+
block.split("\n").flatMap((line, index) => index === 0 ? [text3(line)] : [element("br", []), text3(line)])
|
|
1681
|
+
)
|
|
1682
|
+
);
|
|
1683
|
+
var formatDuration = (seconds) => {
|
|
1684
|
+
const hours = Math.floor(seconds / 3600);
|
|
1685
|
+
const minutes = Math.floor(seconds % 3600 / 60);
|
|
1686
|
+
const remainder = seconds % 60;
|
|
1687
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
1688
|
+
return hours > 0 ? `${hours}:${pad(minutes)}:${pad(remainder)}` : `${minutes}:${pad(remainder)}`;
|
|
1689
|
+
};
|
|
1690
|
+
var metadataList = (details) => {
|
|
1691
|
+
const items = [];
|
|
1692
|
+
if (details.author) {
|
|
1693
|
+
items.push(element("li", [text3(`Channel: ${details.author}`)]));
|
|
1694
|
+
}
|
|
1695
|
+
const seconds = Number(details.lengthSeconds);
|
|
1696
|
+
if (Number.isFinite(seconds) && seconds > 0) {
|
|
1697
|
+
items.push(element("li", [text3(`Duration: ${formatDuration(seconds)}`)]));
|
|
1698
|
+
}
|
|
1699
|
+
const views = Number(details.viewCount);
|
|
1700
|
+
if (Number.isFinite(views) && views > 0) {
|
|
1701
|
+
items.push(element("li", [text3(`Views: ${views.toLocaleString("en-US")}`)]));
|
|
1702
|
+
}
|
|
1703
|
+
return items.length > 0 ? element("ul", items) : void 0;
|
|
1704
|
+
};
|
|
1705
|
+
var youtubeAdapter = {
|
|
1706
|
+
id: "youtube",
|
|
1707
|
+
matches: ({ url }) => {
|
|
1708
|
+
if (!url) {
|
|
1709
|
+
return false;
|
|
1710
|
+
}
|
|
1711
|
+
try {
|
|
1712
|
+
return /(^|\.)(youtube\.com|youtu\.be|youtube-nocookie\.com)$/.test(new URL(url).hostname);
|
|
1713
|
+
} catch {
|
|
1714
|
+
return false;
|
|
1715
|
+
}
|
|
1716
|
+
},
|
|
1717
|
+
// Scanning every script body for the JSON blob would cost a full traversal on every page, so
|
|
1718
|
+
// the fallback keys off markers the signature already collected.
|
|
1719
|
+
matchesFingerprint: ({ signature }) => Boolean(signature?.ids.has("movie_player") || signature?.classes.has("ytd-app")),
|
|
1720
|
+
extract: ({ hast }) => {
|
|
1721
|
+
const player = findPlayerResponse(hast);
|
|
1722
|
+
const details = player?.videoDetails;
|
|
1723
|
+
if (!details?.title) {
|
|
1724
|
+
return void 0;
|
|
1725
|
+
}
|
|
1726
|
+
const children = [heading(1, details.title)];
|
|
1727
|
+
const metadata = metadataList(details);
|
|
1728
|
+
if (metadata) {
|
|
1729
|
+
children.push(metadata);
|
|
1730
|
+
}
|
|
1731
|
+
const description = details.shortDescription?.trim();
|
|
1732
|
+
if (description) {
|
|
1733
|
+
children.push(heading(2, "Description"));
|
|
1734
|
+
children.push(...descriptionNodes(description));
|
|
1735
|
+
}
|
|
1736
|
+
const tracks = youtubeCaptionTracks(hast);
|
|
1737
|
+
if (tracks.length > 0) {
|
|
1738
|
+
const languages2 = [...new Set(tracks.map((track) => track.languageCode))].sort();
|
|
1739
|
+
children.push(paragraph(`Captions available: ${languages2.join(", ")}`));
|
|
1740
|
+
}
|
|
1741
|
+
return { type: "root", children };
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1744
|
+
|
|
1745
|
+
// src/detect-client-shell.ts
|
|
1746
|
+
var SHELL_SUSPICION_THRESHOLD = 300;
|
|
1747
|
+
var SHELL_EMPTY_THRESHOLD = 50;
|
|
1748
|
+
var BODY_PATTERN = /<body[^>]*>([\s\S]*?)<\/body>/i;
|
|
1749
|
+
var HEAD_PATTERN = /<head[^>]*>[\s\S]*?<\/head>/gi;
|
|
1750
|
+
var NOSCRIPT_PATTERN = /<noscript[^>]*>([\s\S]*?)<\/noscript>/gi;
|
|
1751
|
+
var INVISIBLE_ELEMENT_PATTERN = /<(script|style|template|svg|noscript)[^>]*>[\s\S]*?<\/\1>/gi;
|
|
1752
|
+
var COMMENT_PATTERN = /<!--[\s\S]*?-->/g;
|
|
1753
|
+
var TAG_PATTERN = /<[^>]+>/g;
|
|
1754
|
+
var MOUNT_POINT_PATTERN = /<[a-z][^>]*\sid=["'](?:root|app|__next|___gatsby|__nuxt|q-app|svelte|react-root)["']|<app-root[\s>]|\sdata-reactroot[\s>=]/i;
|
|
1755
|
+
var CHALLENGE_PATTERN = /cf-chl|challenge-platform|cf_chl_opt|just a moment|attention required|checking your browser|_incapsula_|px-captcha|datadome/i;
|
|
1756
|
+
var ENTITIES = {
|
|
1757
|
+
" ": " ",
|
|
1758
|
+
"&": "&",
|
|
1759
|
+
"<": "<",
|
|
1760
|
+
">": ">",
|
|
1761
|
+
""": '"',
|
|
1762
|
+
"'": "'"
|
|
1763
|
+
};
|
|
1764
|
+
var visibleTextOf = (markup) => markup.replace(COMMENT_PATTERN, " ").replace(INVISIBLE_ELEMENT_PATTERN, " ").replace(TAG_PATTERN, " ").replace(/&(?:nbsp|amp|lt|gt|quot|#39);/g, (entity) => ENTITIES[entity] ?? " ").replace(/\s+/g, " ").trim();
|
|
1765
|
+
var noscriptMentionsJavascript = (html) => {
|
|
1766
|
+
for (const match of html.matchAll(NOSCRIPT_PATTERN)) {
|
|
1767
|
+
if (/javascript/i.test(match[1] ?? "")) {
|
|
1768
|
+
return true;
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
return false;
|
|
1772
|
+
};
|
|
1773
|
+
var detectClientShell = (html) => {
|
|
1774
|
+
const body = BODY_PATTERN.exec(html)?.[1] ?? html.replace(HEAD_PATTERN, " ");
|
|
1775
|
+
const visibleTextLength = visibleTextOf(body).length;
|
|
1776
|
+
if (visibleTextLength >= SHELL_SUSPICION_THRESHOLD) {
|
|
1777
|
+
return { isShell: false, visibleTextLength };
|
|
1778
|
+
}
|
|
1779
|
+
if (CHALLENGE_PATTERN.test(html)) {
|
|
1780
|
+
return { isShell: true, reason: "anti-bot-challenge", visibleTextLength };
|
|
1781
|
+
}
|
|
1782
|
+
if (MOUNT_POINT_PATTERN.test(body)) {
|
|
1783
|
+
return { isShell: true, reason: "spa-shell", visibleTextLength };
|
|
1784
|
+
}
|
|
1785
|
+
if (noscriptMentionsJavascript(html)) {
|
|
1786
|
+
return { isShell: true, reason: "noscript-only", visibleTextLength };
|
|
1787
|
+
}
|
|
1788
|
+
if (visibleTextLength < SHELL_EMPTY_THRESHOLD) {
|
|
1789
|
+
return { isShell: true, reason: "empty-body", visibleTextLength };
|
|
1790
|
+
}
|
|
1791
|
+
return { isShell: false, visibleTextLength };
|
|
1792
|
+
};
|
|
1793
|
+
|
|
1794
|
+
// src/utils/parse-html.ts
|
|
1363
1795
|
var MAX_ELEMENT_DEPTH = 256;
|
|
1364
|
-
var
|
|
1796
|
+
var isParent2 = (node) => "childNodes" in node;
|
|
1365
1797
|
var LINE_ELEMENTS = /* @__PURE__ */ new Set([
|
|
1366
1798
|
"p",
|
|
1367
1799
|
"div",
|
|
@@ -1402,7 +1834,7 @@ var flattenDeep = (root, maxDepth) => {
|
|
|
1402
1834
|
let unwrapped = false;
|
|
1403
1835
|
for (let index = 0; index < children.length; index++) {
|
|
1404
1836
|
const child = children[index];
|
|
1405
|
-
if (!
|
|
1837
|
+
if (!isParent2(child)) {
|
|
1406
1838
|
continue;
|
|
1407
1839
|
}
|
|
1408
1840
|
if (depth + 1 > maxDepth && "tagName" in child) {
|
|
@@ -1426,12 +1858,78 @@ var flattenDeep = (root, maxDepth) => {
|
|
|
1426
1858
|
}
|
|
1427
1859
|
}
|
|
1428
1860
|
};
|
|
1429
|
-
var
|
|
1861
|
+
var SCRIPT_OR_STYLE_START = /<!--|<(script|style)(?=[\s/>])/gi;
|
|
1862
|
+
var CLOSING_TAG = {
|
|
1863
|
+
script: /<\/script[\s/>]/gi,
|
|
1864
|
+
style: /<\/style[\s/>]/gi
|
|
1865
|
+
};
|
|
1866
|
+
var startTagEnd = (html, from) => {
|
|
1867
|
+
let index = from;
|
|
1868
|
+
while (index < html.length) {
|
|
1869
|
+
const char = html[index];
|
|
1870
|
+
if (char === ">") {
|
|
1871
|
+
return index + 1;
|
|
1872
|
+
}
|
|
1873
|
+
if (char === "=") {
|
|
1874
|
+
let value = index + 1;
|
|
1875
|
+
while (value < html.length && /\s/.test(html[value] ?? "")) {
|
|
1876
|
+
value++;
|
|
1877
|
+
}
|
|
1878
|
+
const quote = html[value];
|
|
1879
|
+
if (quote === '"' || quote === "'") {
|
|
1880
|
+
const close = html.indexOf(quote, value + 1);
|
|
1881
|
+
if (close < 0) {
|
|
1882
|
+
return -1;
|
|
1883
|
+
}
|
|
1884
|
+
index = close + 1;
|
|
1885
|
+
continue;
|
|
1886
|
+
}
|
|
1887
|
+
index = value;
|
|
1888
|
+
continue;
|
|
1889
|
+
}
|
|
1890
|
+
index++;
|
|
1891
|
+
}
|
|
1892
|
+
return -1;
|
|
1893
|
+
};
|
|
1894
|
+
var isReadScript = (attributes, element4) => /application\/ld\+json/i.test(attributes) || element4.includes(PLAYER_RESPONSE_VARIABLE) || CHALLENGE_PATTERN.test(element4);
|
|
1430
1895
|
var LARGE_DOCUMENT_CHARS = 2e6;
|
|
1431
|
-
var stripScriptBodies = (html) =>
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
);
|
|
1896
|
+
var stripScriptBodies = (html) => {
|
|
1897
|
+
const out = [];
|
|
1898
|
+
let copied = 0;
|
|
1899
|
+
const start = new RegExp(SCRIPT_OR_STYLE_START);
|
|
1900
|
+
for (let match = start.exec(html); match; match = start.exec(html)) {
|
|
1901
|
+
if (match[1] === void 0) {
|
|
1902
|
+
const commentEnd = html.indexOf("-->", match.index + 4);
|
|
1903
|
+
if (commentEnd < 0) {
|
|
1904
|
+
break;
|
|
1905
|
+
}
|
|
1906
|
+
start.lastIndex = commentEnd + 3;
|
|
1907
|
+
continue;
|
|
1908
|
+
}
|
|
1909
|
+
const tag = match[1].toLowerCase();
|
|
1910
|
+
const bodyStart = startTagEnd(html, match.index + match[0].length);
|
|
1911
|
+
if (bodyStart < 0) {
|
|
1912
|
+
break;
|
|
1913
|
+
}
|
|
1914
|
+
const closing = CLOSING_TAG[tag];
|
|
1915
|
+
closing.lastIndex = bodyStart;
|
|
1916
|
+
const close = closing.exec(html);
|
|
1917
|
+
if (!close) {
|
|
1918
|
+
break;
|
|
1919
|
+
}
|
|
1920
|
+
const tagEnd = html.indexOf(">", close.index);
|
|
1921
|
+
const elementEnd = tagEnd < 0 ? html.length : tagEnd + 1;
|
|
1922
|
+
const element4 = html.slice(match.index, elementEnd);
|
|
1923
|
+
const openingTag = html.slice(match.index, bodyStart);
|
|
1924
|
+
if (!(tag === "script" && isReadScript(openingTag, element4))) {
|
|
1925
|
+
out.push(html.slice(copied, bodyStart), html.slice(close.index, elementEnd));
|
|
1926
|
+
copied = elementEnd;
|
|
1927
|
+
}
|
|
1928
|
+
start.lastIndex = elementEnd;
|
|
1929
|
+
}
|
|
1930
|
+
out.push(html.slice(copied));
|
|
1931
|
+
return out.join("");
|
|
1932
|
+
};
|
|
1435
1933
|
var parseHtml = (html, options = {}) => {
|
|
1436
1934
|
const source = html.length > LARGE_DOCUMENT_CHARS ? stripScriptBodies(html) : html;
|
|
1437
1935
|
const document2 = options.fragment ? parseFragment(source, { scriptingEnabled: false }) : parse(source, { scriptingEnabled: false });
|
|
@@ -1617,246 +2115,24 @@ var hoistAttentionWhitespace = (node) => {
|
|
|
1617
2115
|
if (lead) {
|
|
1618
2116
|
index += spaceBeside(parent.children, index, "before");
|
|
1619
2117
|
}
|
|
1620
|
-
if (trail) {
|
|
1621
|
-
spaceBeside(parent.children, index, "after");
|
|
1622
|
-
}
|
|
1623
|
-
}
|
|
1624
|
-
};
|
|
1625
|
-
var extractMdast = (node) => {
|
|
1626
|
-
const extracted = filter(node, (node2) => {
|
|
1627
|
-
if (!emptyDeclarationFilter(node2)) {
|
|
1628
|
-
return false;
|
|
1629
|
-
}
|
|
1630
|
-
return true;
|
|
1631
|
-
});
|
|
1632
|
-
if (extracted) {
|
|
1633
|
-
liftDisplayMathIn(extracted);
|
|
1634
|
-
tidyBreaks(extracted);
|
|
1635
|
-
hoistAttentionWhitespace(extracted);
|
|
1636
|
-
}
|
|
1637
|
-
return extracted;
|
|
1638
|
-
};
|
|
1639
|
-
|
|
1640
|
-
// src/utils/hast-fast.ts
|
|
1641
|
-
var EMPTY_METRICS = { text: 0, link: 0, commas: 0, paragraphs: 0, elements: 0 };
|
|
1642
|
-
var COMMA_REGEX = /[,、,،]/g;
|
|
1643
|
-
var PARAGRAPH_LIKE = /* @__PURE__ */ new Set(["p", "pre", "li", "blockquote", "figcaption", "dd", "dt"]);
|
|
1644
|
-
var NON_PROSE_TAGS = /* @__PURE__ */ new Set(["script", "style", "noscript", "template", "svg", "math", "iframe", "object"]);
|
|
1645
|
-
var isElement = (node) => typeof node === "object" && node !== null && node.type === "element";
|
|
1646
|
-
var isParent2 = (node) => typeof node === "object" && node !== null && Array.isArray(node.children);
|
|
1647
|
-
var classList = (element4) => {
|
|
1648
|
-
const className = element4.properties?.className;
|
|
1649
|
-
if (Array.isArray(className)) {
|
|
1650
|
-
return className;
|
|
1651
|
-
}
|
|
1652
|
-
if (typeof className === "string") {
|
|
1653
|
-
return className.split(/\s+/).filter(Boolean);
|
|
1654
|
-
}
|
|
1655
|
-
return [];
|
|
1656
|
-
};
|
|
1657
|
-
var matchString = (element4) => {
|
|
1658
|
-
const id = element4.properties?.id;
|
|
1659
|
-
const classes = classList(element4);
|
|
1660
|
-
if (!id && classes.length === 0) {
|
|
1661
|
-
return element4.tagName;
|
|
1662
|
-
}
|
|
1663
|
-
return `${element4.tagName} ${typeof id === "string" ? id : ""} ${classes.join(" ")}`;
|
|
1664
|
-
};
|
|
1665
|
-
var MetricsCollector = class {
|
|
1666
|
-
#cache = /* @__PURE__ */ new WeakMap();
|
|
1667
|
-
metrics(node) {
|
|
1668
|
-
if (node.type === "text") {
|
|
1669
|
-
const value = node.value;
|
|
1670
|
-
if (value.trim().length === 0) {
|
|
1671
|
-
return EMPTY_METRICS;
|
|
1672
|
-
}
|
|
1673
|
-
return {
|
|
1674
|
-
text: value.length,
|
|
1675
|
-
link: 0,
|
|
1676
|
-
commas: (value.match(COMMA_REGEX) ?? []).length,
|
|
1677
|
-
paragraphs: 0,
|
|
1678
|
-
elements: 0
|
|
1679
|
-
};
|
|
1680
|
-
}
|
|
1681
|
-
if (!isParent2(node)) {
|
|
1682
|
-
return EMPTY_METRICS;
|
|
1683
|
-
}
|
|
1684
|
-
const cached = this.#cache.get(node);
|
|
1685
|
-
if (cached) {
|
|
1686
|
-
return cached;
|
|
1687
|
-
}
|
|
1688
|
-
if (isElement(node) && NON_PROSE_TAGS.has(node.tagName)) {
|
|
1689
|
-
this.#cache.set(node, EMPTY_METRICS);
|
|
1690
|
-
return EMPTY_METRICS;
|
|
1691
|
-
}
|
|
1692
|
-
let text7 = 0;
|
|
1693
|
-
let link = 0;
|
|
1694
|
-
let commas = 0;
|
|
1695
|
-
let paragraphs = 0;
|
|
1696
|
-
let elements = 0;
|
|
1697
|
-
for (const child of node.children) {
|
|
1698
|
-
const childMetrics = this.metrics(child);
|
|
1699
|
-
text7 += childMetrics.text;
|
|
1700
|
-
link += childMetrics.link;
|
|
1701
|
-
commas += childMetrics.commas;
|
|
1702
|
-
paragraphs += childMetrics.paragraphs;
|
|
1703
|
-
elements += childMetrics.elements;
|
|
1704
|
-
if (isElement(child)) {
|
|
1705
|
-
elements += 1;
|
|
1706
|
-
if (PARAGRAPH_LIKE.has(child.tagName)) {
|
|
1707
|
-
paragraphs += 1;
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
1710
|
-
}
|
|
1711
|
-
if (isElement(node) && node.tagName === "a") {
|
|
1712
|
-
link = text7;
|
|
1713
|
-
}
|
|
1714
|
-
const metrics = { text: text7, link, commas, paragraphs, elements };
|
|
1715
|
-
this.#cache.set(node, metrics);
|
|
1716
|
-
return metrics;
|
|
1717
|
-
}
|
|
1718
|
-
textLength(node) {
|
|
1719
|
-
return this.metrics(node).text;
|
|
1720
|
-
}
|
|
1721
|
-
/** Fraction of a subtree's text that is link anchor text. `0` for empty subtrees. */
|
|
1722
|
-
linkDensity(node) {
|
|
1723
|
-
const { text: text7, link } = this.metrics(node);
|
|
1724
|
-
if (text7 === 0) {
|
|
1725
|
-
return 0;
|
|
1726
|
-
}
|
|
1727
|
-
return Math.min(1, link / text7);
|
|
1728
|
-
}
|
|
1729
|
-
};
|
|
1730
|
-
var pruneInPlace = (tree, keep) => {
|
|
1731
|
-
if (!isParent2(tree)) {
|
|
1732
|
-
return tree;
|
|
1733
|
-
}
|
|
1734
|
-
const stack = [tree];
|
|
1735
|
-
while (stack.length > 0) {
|
|
1736
|
-
const parent = stack.pop();
|
|
1737
|
-
const children = parent.children;
|
|
1738
|
-
let write = 0;
|
|
1739
|
-
for (let read = 0; read < children.length; read++) {
|
|
1740
|
-
const child = children[read];
|
|
1741
|
-
if (!keep(child, parent)) {
|
|
1742
|
-
continue;
|
|
1743
|
-
}
|
|
1744
|
-
children[write++] = child;
|
|
1745
|
-
if (isParent2(child)) {
|
|
1746
|
-
stack.push(child);
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
|
-
children.length = write;
|
|
1750
|
-
}
|
|
1751
|
-
return tree;
|
|
1752
|
-
};
|
|
1753
|
-
var walk = (tree, visit) => {
|
|
1754
|
-
const stack = [{ node: tree, parent: void 0 }];
|
|
1755
|
-
while (stack.length > 0) {
|
|
1756
|
-
const { node, parent } = stack.pop();
|
|
1757
|
-
if (visit(node, parent) === false) {
|
|
1758
|
-
continue;
|
|
1759
|
-
}
|
|
1760
|
-
if (isParent2(node)) {
|
|
1761
|
-
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
1762
|
-
stack.push({ node: node.children[i], parent: node });
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
};
|
|
1767
|
-
var collectElements = (tree, predicate) => {
|
|
1768
|
-
const found = [];
|
|
1769
|
-
walk(tree, (node) => {
|
|
1770
|
-
if (isElement(node) && predicate(node)) {
|
|
1771
|
-
found.push(node);
|
|
1772
|
-
}
|
|
1773
|
-
});
|
|
1774
|
-
return found;
|
|
1775
|
-
};
|
|
1776
|
-
var findElement = (tree, predicate) => {
|
|
1777
|
-
let found;
|
|
1778
|
-
walk(tree, (node) => {
|
|
1779
|
-
if (found) {
|
|
1780
|
-
return false;
|
|
1781
|
-
}
|
|
1782
|
-
if (isElement(node) && predicate(node)) {
|
|
1783
|
-
found = node;
|
|
1784
|
-
return false;
|
|
1785
|
-
}
|
|
1786
|
-
});
|
|
1787
|
-
return found;
|
|
1788
|
-
};
|
|
1789
|
-
var cloneHast = (tree) => {
|
|
1790
|
-
try {
|
|
1791
|
-
return structuredClone(tree);
|
|
1792
|
-
} catch {
|
|
1793
|
-
return manualClone(tree);
|
|
1794
|
-
}
|
|
1795
|
-
};
|
|
1796
|
-
var manualClone = (node) => {
|
|
1797
|
-
if (Array.isArray(node)) {
|
|
1798
|
-
return node.map(manualClone);
|
|
1799
|
-
}
|
|
1800
|
-
if (typeof node !== "object" || node === null) {
|
|
1801
|
-
return node;
|
|
1802
|
-
}
|
|
1803
|
-
const copy = {};
|
|
1804
|
-
for (const [key, value] of Object.entries(node)) {
|
|
1805
|
-
copy[key] = manualClone(value);
|
|
1806
|
-
}
|
|
1807
|
-
return copy;
|
|
1808
|
-
};
|
|
1809
|
-
var property = (element4, key) => {
|
|
1810
|
-
const properties = element4.properties;
|
|
1811
|
-
if (!properties) {
|
|
1812
|
-
return void 0;
|
|
1813
|
-
}
|
|
1814
|
-
const direct = properties[key];
|
|
1815
|
-
if (direct !== void 0) {
|
|
1816
|
-
return direct;
|
|
1817
|
-
}
|
|
1818
|
-
if (!(key.includes("-") || key.includes(":"))) {
|
|
1819
|
-
return void 0;
|
|
1820
|
-
}
|
|
1821
|
-
return properties[camelCaseAttribute(key)];
|
|
1822
|
-
};
|
|
1823
|
-
var camelCaseAttribute = (key) => key.replace(/[-:]([a-z])/g, (_, character) => character.toUpperCase());
|
|
1824
|
-
var numericProperty = (element4, key) => {
|
|
1825
|
-
const raw = property(element4, key);
|
|
1826
|
-
if (typeof raw === "number") {
|
|
1827
|
-
return Number.isFinite(raw) ? raw : void 0;
|
|
1828
|
-
}
|
|
1829
|
-
if (typeof raw === "string" && raw.length > 0) {
|
|
1830
|
-
const parsed = Number.parseFloat(raw);
|
|
1831
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
1832
|
-
}
|
|
1833
|
-
return void 0;
|
|
1834
|
-
};
|
|
1835
|
-
var stringProperty = (element4, key) => {
|
|
1836
|
-
const raw = property(element4, key);
|
|
1837
|
-
if (typeof raw === "string") {
|
|
1838
|
-
return raw;
|
|
1839
|
-
}
|
|
1840
|
-
if (Array.isArray(raw)) {
|
|
1841
|
-
return raw.join(" ");
|
|
1842
|
-
}
|
|
1843
|
-
if (raw === true) {
|
|
1844
|
-
return "";
|
|
1845
|
-
}
|
|
1846
|
-
return void 0;
|
|
1847
|
-
};
|
|
1848
|
-
var isTruthyAttribute = (element4, key) => {
|
|
1849
|
-
const raw = property(element4, key);
|
|
1850
|
-
if (raw === void 0 || raw === null || raw === false) {
|
|
1851
|
-
return false;
|
|
2118
|
+
if (trail) {
|
|
2119
|
+
spaceBeside(parent.children, index, "after");
|
|
2120
|
+
}
|
|
1852
2121
|
}
|
|
1853
|
-
|
|
2122
|
+
};
|
|
2123
|
+
var extractMdast = (node) => {
|
|
2124
|
+
const extracted = filter(node, (node2) => {
|
|
2125
|
+
if (!emptyDeclarationFilter(node2)) {
|
|
2126
|
+
return false;
|
|
2127
|
+
}
|
|
1854
2128
|
return true;
|
|
2129
|
+
});
|
|
2130
|
+
if (extracted) {
|
|
2131
|
+
liftDisplayMathIn(extracted);
|
|
2132
|
+
tidyBreaks(extracted);
|
|
2133
|
+
hoistAttentionWhitespace(extracted);
|
|
1855
2134
|
}
|
|
1856
|
-
|
|
1857
|
-
return raw.toLowerCase() !== "false";
|
|
1858
|
-
}
|
|
1859
|
-
return true;
|
|
2135
|
+
return extracted;
|
|
1860
2136
|
};
|
|
1861
2137
|
|
|
1862
2138
|
// src/extractors/lib/code-tabs.ts
|
|
@@ -3057,8 +3333,8 @@ var DECLARATIVE_ADAPTERS = SPECS.map(defineSelectorAdapter);
|
|
|
3057
3333
|
// src/adapters/sites/hackernews.ts
|
|
3058
3334
|
import { select as select4, selectAll as selectAll2 } from "hast-util-select";
|
|
3059
3335
|
var MAX_QUOTE_DEPTH = 6;
|
|
3060
|
-
var
|
|
3061
|
-
var
|
|
3336
|
+
var text4 = (value) => ({ type: "text", value });
|
|
3337
|
+
var element2 = (tagName, children) => ({
|
|
3062
3338
|
type: "element",
|
|
3063
3339
|
tagName,
|
|
3064
3340
|
properties: {},
|
|
@@ -3080,7 +3356,7 @@ var commentDepth = (row) => {
|
|
|
3080
3356
|
var nest = (content, depth) => {
|
|
3081
3357
|
let current = content;
|
|
3082
3358
|
for (let level = 0; level < Math.min(depth, MAX_QUOTE_DEPTH); level++) {
|
|
3083
|
-
current = [
|
|
3359
|
+
current = [element2("blockquote", current)];
|
|
3084
3360
|
}
|
|
3085
3361
|
return current[0];
|
|
3086
3362
|
};
|
|
@@ -3088,15 +3364,15 @@ var storyNodes = (root) => {
|
|
|
3088
3364
|
const nodes = [];
|
|
3089
3365
|
const titleLink = select4(".titleline > a", root);
|
|
3090
3366
|
if (titleLink) {
|
|
3091
|
-
nodes.push(
|
|
3367
|
+
nodes.push(element2("h1", [...titleLink.children]));
|
|
3092
3368
|
const href = stringProperty(titleLink, "href");
|
|
3093
3369
|
if (href && /^https?:/.test(href)) {
|
|
3094
|
-
nodes.push(
|
|
3370
|
+
nodes.push(element2("p", [{ ...titleLink, children: [text4(href)] }]));
|
|
3095
3371
|
}
|
|
3096
3372
|
}
|
|
3097
3373
|
const topText = select4(".toptext", root);
|
|
3098
3374
|
if (topText) {
|
|
3099
|
-
nodes.push(
|
|
3375
|
+
nodes.push(element2("div", [...topText.children]));
|
|
3100
3376
|
}
|
|
3101
3377
|
return nodes;
|
|
3102
3378
|
};
|
|
@@ -3108,10 +3384,10 @@ var commentNodes = (root) => {
|
|
|
3108
3384
|
continue;
|
|
3109
3385
|
}
|
|
3110
3386
|
const author = select4(".hnuser", row);
|
|
3111
|
-
const header = author ? [
|
|
3387
|
+
const header = author ? [element2("strong", [...author.children])] : [];
|
|
3112
3388
|
const content = [
|
|
3113
|
-
...header.length > 0 ? [
|
|
3114
|
-
|
|
3389
|
+
...header.length > 0 ? [element2("p", header)] : [],
|
|
3390
|
+
element2("div", [...body.children])
|
|
3115
3391
|
];
|
|
3116
3392
|
nodes.push(nest(content, commentDepth(row)));
|
|
3117
3393
|
}
|
|
@@ -3138,171 +3414,13 @@ var hackernewsAdapter = {
|
|
|
3138
3414
|
}
|
|
3139
3415
|
const children = [...story];
|
|
3140
3416
|
if (comments.length > 0) {
|
|
3141
|
-
children.push(
|
|
3417
|
+
children.push(element2("h2", [text4("Comments")]));
|
|
3142
3418
|
children.push(...comments);
|
|
3143
3419
|
}
|
|
3144
3420
|
return { type: "root", children };
|
|
3145
3421
|
}
|
|
3146
3422
|
};
|
|
3147
3423
|
|
|
3148
|
-
// src/adapters/sites/youtube.ts
|
|
3149
|
-
var findObjectEnd = (source, start) => {
|
|
3150
|
-
let depth = 0;
|
|
3151
|
-
let inString = false;
|
|
3152
|
-
let escaped = false;
|
|
3153
|
-
for (let index = start; index < source.length; index++) {
|
|
3154
|
-
const character = source[index];
|
|
3155
|
-
if (escaped) {
|
|
3156
|
-
escaped = false;
|
|
3157
|
-
} else if (character === "\\") {
|
|
3158
|
-
escaped = true;
|
|
3159
|
-
} else if (character === '"') {
|
|
3160
|
-
inString = !inString;
|
|
3161
|
-
} else if (!inString) {
|
|
3162
|
-
if (character === "{") {
|
|
3163
|
-
depth += 1;
|
|
3164
|
-
} else if (character === "}" && --depth === 0) {
|
|
3165
|
-
return index + 1;
|
|
3166
|
-
}
|
|
3167
|
-
}
|
|
3168
|
-
}
|
|
3169
|
-
return -1;
|
|
3170
|
-
};
|
|
3171
|
-
var extractAssignedJson = (source, name) => {
|
|
3172
|
-
const markerIndex = source.indexOf(`${name} =`);
|
|
3173
|
-
if (markerIndex === -1) {
|
|
3174
|
-
return void 0;
|
|
3175
|
-
}
|
|
3176
|
-
const start = source.indexOf("{", markerIndex);
|
|
3177
|
-
if (start === -1) {
|
|
3178
|
-
return void 0;
|
|
3179
|
-
}
|
|
3180
|
-
const end = findObjectEnd(source, start);
|
|
3181
|
-
if (end === -1) {
|
|
3182
|
-
return void 0;
|
|
3183
|
-
}
|
|
3184
|
-
try {
|
|
3185
|
-
return JSON.parse(source.slice(start, end));
|
|
3186
|
-
} catch {
|
|
3187
|
-
return void 0;
|
|
3188
|
-
}
|
|
3189
|
-
};
|
|
3190
|
-
var scriptText = (root) => collectElements(root, (element4) => element4.tagName === "script").map(
|
|
3191
|
-
(script) => script.children.map((child) => child.type === "text" ? child.value : "").join("")
|
|
3192
|
-
);
|
|
3193
|
-
var findPlayerResponse = (root) => {
|
|
3194
|
-
for (const source of scriptText(root)) {
|
|
3195
|
-
if (!source.includes("ytInitialPlayerResponse")) {
|
|
3196
|
-
continue;
|
|
3197
|
-
}
|
|
3198
|
-
const parsed = extractAssignedJson(source, "ytInitialPlayerResponse");
|
|
3199
|
-
if (parsed && typeof parsed === "object") {
|
|
3200
|
-
return parsed;
|
|
3201
|
-
}
|
|
3202
|
-
}
|
|
3203
|
-
return void 0;
|
|
3204
|
-
};
|
|
3205
|
-
var youtubeCaptionTracks = (root) => {
|
|
3206
|
-
const player = findPlayerResponse(root);
|
|
3207
|
-
const renderer = player?.captions?.playerCaptionsTracklistRenderer;
|
|
3208
|
-
const tracks = renderer?.captionTracks;
|
|
3209
|
-
if (!Array.isArray(tracks)) {
|
|
3210
|
-
return [];
|
|
3211
|
-
}
|
|
3212
|
-
return tracks.flatMap((track) => {
|
|
3213
|
-
const url = typeof track.baseUrl === "string" ? track.baseUrl : void 0;
|
|
3214
|
-
const languageCode = typeof track.languageCode === "string" ? track.languageCode : void 0;
|
|
3215
|
-
if (!(url && languageCode)) {
|
|
3216
|
-
return [];
|
|
3217
|
-
}
|
|
3218
|
-
const name = track.name?.simpleText;
|
|
3219
|
-
return [
|
|
3220
|
-
{
|
|
3221
|
-
url,
|
|
3222
|
-
languageCode,
|
|
3223
|
-
name: typeof name === "string" ? name : void 0,
|
|
3224
|
-
isAutomatic: track.kind === "asr"
|
|
3225
|
-
}
|
|
3226
|
-
];
|
|
3227
|
-
});
|
|
3228
|
-
};
|
|
3229
|
-
var text4 = (value) => ({ type: "text", value });
|
|
3230
|
-
var element2 = (tagName, children) => ({
|
|
3231
|
-
type: "element",
|
|
3232
|
-
tagName,
|
|
3233
|
-
properties: {},
|
|
3234
|
-
children
|
|
3235
|
-
});
|
|
3236
|
-
var heading = (depth, value) => element2(`h${depth}`, [text4(value)]);
|
|
3237
|
-
var paragraph = (value) => element2("p", [text4(value)]);
|
|
3238
|
-
var descriptionNodes = (description) => description.split(/\n{2,}/).map((block) => block.trim()).filter((block) => block.length > 0).map(
|
|
3239
|
-
(block) => element2(
|
|
3240
|
-
"p",
|
|
3241
|
-
block.split("\n").flatMap((line, index) => index === 0 ? [text4(line)] : [element2("br", []), text4(line)])
|
|
3242
|
-
)
|
|
3243
|
-
);
|
|
3244
|
-
var formatDuration = (seconds) => {
|
|
3245
|
-
const hours = Math.floor(seconds / 3600);
|
|
3246
|
-
const minutes = Math.floor(seconds % 3600 / 60);
|
|
3247
|
-
const remainder = seconds % 60;
|
|
3248
|
-
const pad = (value) => String(value).padStart(2, "0");
|
|
3249
|
-
return hours > 0 ? `${hours}:${pad(minutes)}:${pad(remainder)}` : `${minutes}:${pad(remainder)}`;
|
|
3250
|
-
};
|
|
3251
|
-
var metadataList = (details) => {
|
|
3252
|
-
const items = [];
|
|
3253
|
-
if (details.author) {
|
|
3254
|
-
items.push(element2("li", [text4(`Channel: ${details.author}`)]));
|
|
3255
|
-
}
|
|
3256
|
-
const seconds = Number(details.lengthSeconds);
|
|
3257
|
-
if (Number.isFinite(seconds) && seconds > 0) {
|
|
3258
|
-
items.push(element2("li", [text4(`Duration: ${formatDuration(seconds)}`)]));
|
|
3259
|
-
}
|
|
3260
|
-
const views = Number(details.viewCount);
|
|
3261
|
-
if (Number.isFinite(views) && views > 0) {
|
|
3262
|
-
items.push(element2("li", [text4(`Views: ${views.toLocaleString("en-US")}`)]));
|
|
3263
|
-
}
|
|
3264
|
-
return items.length > 0 ? element2("ul", items) : void 0;
|
|
3265
|
-
};
|
|
3266
|
-
var youtubeAdapter = {
|
|
3267
|
-
id: "youtube",
|
|
3268
|
-
matches: ({ url }) => {
|
|
3269
|
-
if (!url) {
|
|
3270
|
-
return false;
|
|
3271
|
-
}
|
|
3272
|
-
try {
|
|
3273
|
-
return /(^|\.)(youtube\.com|youtu\.be|youtube-nocookie\.com)$/.test(new URL(url).hostname);
|
|
3274
|
-
} catch {
|
|
3275
|
-
return false;
|
|
3276
|
-
}
|
|
3277
|
-
},
|
|
3278
|
-
// Scanning every script body for the JSON blob would cost a full traversal on every page, so
|
|
3279
|
-
// the fallback keys off markers the signature already collected.
|
|
3280
|
-
matchesFingerprint: ({ signature }) => Boolean(signature?.ids.has("movie_player") || signature?.classes.has("ytd-app")),
|
|
3281
|
-
extract: ({ hast }) => {
|
|
3282
|
-
const player = findPlayerResponse(hast);
|
|
3283
|
-
const details = player?.videoDetails;
|
|
3284
|
-
if (!details?.title) {
|
|
3285
|
-
return void 0;
|
|
3286
|
-
}
|
|
3287
|
-
const children = [heading(1, details.title)];
|
|
3288
|
-
const metadata = metadataList(details);
|
|
3289
|
-
if (metadata) {
|
|
3290
|
-
children.push(metadata);
|
|
3291
|
-
}
|
|
3292
|
-
const description = details.shortDescription?.trim();
|
|
3293
|
-
if (description) {
|
|
3294
|
-
children.push(heading(2, "Description"));
|
|
3295
|
-
children.push(...descriptionNodes(description));
|
|
3296
|
-
}
|
|
3297
|
-
const tracks = youtubeCaptionTracks(hast);
|
|
3298
|
-
if (tracks.length > 0) {
|
|
3299
|
-
const languages2 = [...new Set(tracks.map((track) => track.languageCode))].sort();
|
|
3300
|
-
children.push(paragraph(`Captions available: ${languages2.join(", ")}`));
|
|
3301
|
-
}
|
|
3302
|
-
return { type: "root", children };
|
|
3303
|
-
}
|
|
3304
|
-
};
|
|
3305
|
-
|
|
3306
3424
|
// src/adapters/index.ts
|
|
3307
3425
|
var BUILTIN_ADAPTERS = [youtubeAdapter, hackernewsAdapter, ...DECLARATIVE_ADAPTERS];
|
|
3308
3426
|
var MIN_ADAPTER_TEXT = 40;
|
|
@@ -4826,6 +4944,8 @@ var NEURAL_MODEL = { "kind": "block-gru", "buckets": 1024, "emb": 16, "hidden":
|
|
|
4826
4944
|
var TERMINATOR_PATTERNS = [
|
|
4827
4945
|
/^was this (page|article|document) helpful/i,
|
|
4828
4946
|
/^thanks for the feedback/i,
|
|
4947
|
+
// Site-generator credit closing the page ("Powered by Docsify.js", "Powered by GitBook").
|
|
4948
|
+
/^powered by\b/i,
|
|
4829
4949
|
/^(this page was )?last (modified|updated) (on|at|:)?\s/i,
|
|
4830
4950
|
/^learn how to contribute/i,
|
|
4831
4951
|
/^help (us )?improve\b/i,
|
|
@@ -4855,7 +4975,7 @@ var textOf3 = (node) => {
|
|
|
4855
4975
|
if (node.type === "text") {
|
|
4856
4976
|
return node.value;
|
|
4857
4977
|
}
|
|
4858
|
-
if (!
|
|
4978
|
+
if (!isParent(node)) {
|
|
4859
4979
|
return "";
|
|
4860
4980
|
}
|
|
4861
4981
|
let text7 = "";
|
|
@@ -4873,7 +4993,7 @@ var truncateTrailingBoilerplate = (tree) => {
|
|
|
4873
4993
|
total += node.value.trim().length;
|
|
4874
4994
|
return;
|
|
4875
4995
|
}
|
|
4876
|
-
if (
|
|
4996
|
+
if (isParent(node)) {
|
|
4877
4997
|
for (const child of node.children) {
|
|
4878
4998
|
measure(child);
|
|
4879
4999
|
}
|
|
@@ -4886,7 +5006,7 @@ var truncateTrailingBoilerplate = (tree) => {
|
|
|
4886
5006
|
const tailStart = total * TAIL_START_FRACTION;
|
|
4887
5007
|
let cutoff = -1;
|
|
4888
5008
|
const find = (node) => {
|
|
4889
|
-
if (cutoff !== -1 || !
|
|
5009
|
+
if (cutoff !== -1 || !isParent(node)) {
|
|
4890
5010
|
return;
|
|
4891
5011
|
}
|
|
4892
5012
|
for (const child of node.children) {
|
|
@@ -5846,11 +5966,10 @@ var languages = [
|
|
|
5846
5966
|
["json", [/\b(true|false|null|\{})\b|\"[^"]+\":/g, 10]],
|
|
5847
5967
|
["yaml", [/^(\s+)?[a-z][a-z0-9]*:/gim, 10]]
|
|
5848
5968
|
];
|
|
5849
|
-
var
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
};
|
|
5969
|
+
var MIN_CONFIDENT_SCORE = 400;
|
|
5970
|
+
var detectLanguage = (code) => languages.map(
|
|
5971
|
+
([lang, ...features]) => [lang, features.reduce((acc, [match, score]) => acc + [...code.matchAll(match)].length * score, 0)]
|
|
5972
|
+
).filter(([_, score]) => score >= MIN_CONFIDENT_SCORE).sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
5854
5973
|
|
|
5855
5974
|
// src/utils/pre-text.ts
|
|
5856
5975
|
var LINE_BLOCK_TAGS = /* @__PURE__ */ new Set([
|
|
@@ -5977,17 +6096,46 @@ var errorLine = (error) => {
|
|
|
5977
6096
|
|
|
5978
6097
|
// src/mdast-handlers/custom-code-handler.ts
|
|
5979
6098
|
var LANGUAGE_MATCH_REGEX = [/^language-(\S+)$/, /^highlight-source-(\S+)$/, /^CodeBlock--language-(\S+)$/];
|
|
6099
|
+
var LANGUAGE_TOKEN = /^[\w+#.-]+$/;
|
|
6100
|
+
var NO_LANGUAGE = /* @__PURE__ */ new Set(["plain", "plaintext", "text", "none", "nohighlight"]);
|
|
5980
6101
|
var LANGUAGE_ATTRIBUTES = ["dataLanguage", "dataLang"];
|
|
5981
6102
|
var attributeLanguage = (element4) => {
|
|
5982
6103
|
for (const name of LANGUAGE_ATTRIBUTES) {
|
|
5983
6104
|
const value = element4.properties?.[name];
|
|
5984
|
-
if (typeof value === "string" &&
|
|
6105
|
+
if (typeof value === "string" && LANGUAGE_TOKEN.test(value.trim()) && value.trim() !== "plaintext") {
|
|
5985
6106
|
return value.trim();
|
|
5986
6107
|
}
|
|
5987
6108
|
}
|
|
5988
6109
|
return void 0;
|
|
5989
6110
|
};
|
|
5990
|
-
var
|
|
6111
|
+
var SANDPACK_LANGUAGES = /* @__PURE__ */ new Set([
|
|
6112
|
+
"javascript",
|
|
6113
|
+
"typescript",
|
|
6114
|
+
"jsx",
|
|
6115
|
+
"tsx",
|
|
6116
|
+
"html",
|
|
6117
|
+
"css",
|
|
6118
|
+
"less",
|
|
6119
|
+
"scss",
|
|
6120
|
+
"sass",
|
|
6121
|
+
"json",
|
|
6122
|
+
"markdown",
|
|
6123
|
+
"python",
|
|
6124
|
+
"vue",
|
|
6125
|
+
"svelte"
|
|
6126
|
+
]);
|
|
6127
|
+
var conventionLanguage = (element4) => {
|
|
6128
|
+
const classes = classList(element4);
|
|
6129
|
+
const brushAt = classes.findIndex((name) => name.startsWith("brush:"));
|
|
6130
|
+
if (brushAt >= 0) {
|
|
6131
|
+
const brush = classes[brushAt] === "brush:" ? classes[brushAt + 1] : classes[brushAt].slice("brush:".length);
|
|
6132
|
+
const lang = brush?.toLowerCase();
|
|
6133
|
+
return lang && LANGUAGE_TOKEN.test(lang) && !NO_LANGUAGE.has(lang) ? lang : void 0;
|
|
6134
|
+
}
|
|
6135
|
+
const sandpack = classes.find((name) => name.startsWith("sp-") && SANDPACK_LANGUAGES.has(name.slice(3)));
|
|
6136
|
+
return sandpack?.slice(3);
|
|
6137
|
+
};
|
|
6138
|
+
var codeLanguage = (...elements) => elements.flatMap(classList).map((className) => LANGUAGE_MATCH_REGEX.map((regex) => className.match(regex)).find(Boolean)?.[1]).find(Boolean) ?? elements.map(attributeLanguage).find(Boolean) ?? elements.map(conventionLanguage).find(Boolean);
|
|
5991
6139
|
var codeMeta = (pre) => {
|
|
5992
6140
|
const title = pre.properties?.dataTitle;
|
|
5993
6141
|
if (typeof title !== "string") {
|
|
@@ -6568,6 +6716,82 @@ var removeIconFonts = (tree) => {
|
|
|
6568
6716
|
pruneInPlace(tree, (node) => !isIcon(node));
|
|
6569
6717
|
}
|
|
6570
6718
|
};
|
|
6719
|
+
var textOfNode = (node) => node.type === "text" ? node.value : "children" in node ? node.children.map(textOfNode).join("") : "";
|
|
6720
|
+
var unwrapHeadingAnchors = (tree) => {
|
|
6721
|
+
walk(tree, (node) => {
|
|
6722
|
+
if (!(isElement(node) && /^h[1-6]$/.test(node.tagName))) {
|
|
6723
|
+
return;
|
|
6724
|
+
}
|
|
6725
|
+
const title = textOfNode(node).trim();
|
|
6726
|
+
if (title === "") {
|
|
6727
|
+
return;
|
|
6728
|
+
}
|
|
6729
|
+
const anchorAt = node.children.findIndex(
|
|
6730
|
+
(child) => isElement(child) && child.tagName === "a" && stringProperty(child, "href")?.startsWith("#") && textOfNode(child).trim() === title
|
|
6731
|
+
);
|
|
6732
|
+
const anchor = node.children[anchorAt];
|
|
6733
|
+
if (isElement(anchor)) {
|
|
6734
|
+
node.children.splice(anchorAt, 1, ...anchor.children);
|
|
6735
|
+
}
|
|
6736
|
+
});
|
|
6737
|
+
};
|
|
6738
|
+
var LABEL_LANGUAGES = /* @__PURE__ */ new Set([
|
|
6739
|
+
"js",
|
|
6740
|
+
"javascript",
|
|
6741
|
+
"ts",
|
|
6742
|
+
"typescript",
|
|
6743
|
+
"jsx",
|
|
6744
|
+
"tsx",
|
|
6745
|
+
"html",
|
|
6746
|
+
"css",
|
|
6747
|
+
"scss",
|
|
6748
|
+
"json",
|
|
6749
|
+
"bash",
|
|
6750
|
+
"sh",
|
|
6751
|
+
"shell",
|
|
6752
|
+
"console",
|
|
6753
|
+
"python",
|
|
6754
|
+
"py",
|
|
6755
|
+
"sql",
|
|
6756
|
+
"xml",
|
|
6757
|
+
"yaml",
|
|
6758
|
+
"toml",
|
|
6759
|
+
"http",
|
|
6760
|
+
"rust",
|
|
6761
|
+
"go",
|
|
6762
|
+
"java",
|
|
6763
|
+
"kotlin",
|
|
6764
|
+
"swift",
|
|
6765
|
+
"c",
|
|
6766
|
+
"cpp",
|
|
6767
|
+
"md",
|
|
6768
|
+
"markdown",
|
|
6769
|
+
"diff"
|
|
6770
|
+
]);
|
|
6771
|
+
var LABEL_NO_LANGUAGE = /* @__PURE__ */ new Set(["plain", "plaintext", "text"]);
|
|
6772
|
+
var foldCodeLabels = (tree) => {
|
|
6773
|
+
walk(tree, (node) => {
|
|
6774
|
+
if (!isElement(node) || node.tagName === "pre") {
|
|
6775
|
+
return;
|
|
6776
|
+
}
|
|
6777
|
+
const elements = node.children.filter(isElement);
|
|
6778
|
+
if (elements.length !== 2 || node.children.some((child) => child.type === "text" && child.value.trim() !== "")) {
|
|
6779
|
+
return;
|
|
6780
|
+
}
|
|
6781
|
+
const [label, pre] = elements;
|
|
6782
|
+
if (pre.tagName !== "pre" || /^(h[1-6]|pre)$/.test(label.tagName) || findElement(label, (el) => el.tagName === "pre")) {
|
|
6783
|
+
return;
|
|
6784
|
+
}
|
|
6785
|
+
const name = textOfNode(label).trim().toLowerCase();
|
|
6786
|
+
if (!(LABEL_LANGUAGES.has(name) || LABEL_NO_LANGUAGE.has(name))) {
|
|
6787
|
+
return;
|
|
6788
|
+
}
|
|
6789
|
+
if (LABEL_LANGUAGES.has(name) && pre.properties.dataLanguage === void 0) {
|
|
6790
|
+
pre.properties.dataLanguage = name;
|
|
6791
|
+
}
|
|
6792
|
+
node.children = node.children.filter((child) => child !== label);
|
|
6793
|
+
});
|
|
6794
|
+
};
|
|
6571
6795
|
var SPACED_INLINE = /* @__PURE__ */ new Set(["span", "time", "label"]);
|
|
6572
6796
|
var SCRIPTS = [
|
|
6573
6797
|
["latin", /\p{Script=Latin}/u],
|
|
@@ -6584,7 +6808,6 @@ var needsSpace = (left, right) => {
|
|
|
6584
6808
|
return a !== void 0 && b !== void 0 && a !== b;
|
|
6585
6809
|
};
|
|
6586
6810
|
var NO_SPACING = /* @__PURE__ */ new Set(["pre", "code", "kbd", "samp", "ruby", "math", "svg"]);
|
|
6587
|
-
var textOfNode = (node) => node.type === "text" ? node.value : "children" in node ? node.children.map(textOfNode).join("") : "";
|
|
6588
6811
|
var separateAdjacentInlines = (tree) => {
|
|
6589
6812
|
const visit = (node) => {
|
|
6590
6813
|
if (!("children" in node) || isElement(node) && NO_SPACING.has(node.tagName)) {
|
|
@@ -6605,7 +6828,15 @@ var separateAdjacentInlines = (tree) => {
|
|
|
6605
6828
|
visit(tree);
|
|
6606
6829
|
};
|
|
6607
6830
|
var normalizeHast = (tree, options = {}) => {
|
|
6608
|
-
const {
|
|
6831
|
+
const {
|
|
6832
|
+
images = true,
|
|
6833
|
+
math = true,
|
|
6834
|
+
ariaHeadings = true,
|
|
6835
|
+
iconFonts = true,
|
|
6836
|
+
headingAnchors = true,
|
|
6837
|
+
codeLabels = true,
|
|
6838
|
+
inlineSpacing = true
|
|
6839
|
+
} = options;
|
|
6609
6840
|
if (images) {
|
|
6610
6841
|
resolveImageSources(tree);
|
|
6611
6842
|
}
|
|
@@ -6618,6 +6849,12 @@ var normalizeHast = (tree, options = {}) => {
|
|
|
6618
6849
|
if (iconFonts) {
|
|
6619
6850
|
removeIconFonts(tree);
|
|
6620
6851
|
}
|
|
6852
|
+
if (headingAnchors) {
|
|
6853
|
+
unwrapHeadingAnchors(tree);
|
|
6854
|
+
}
|
|
6855
|
+
if (codeLabels) {
|
|
6856
|
+
foldCodeLabels(tree);
|
|
6857
|
+
}
|
|
6621
6858
|
if (inlineSpacing) {
|
|
6622
6859
|
separateAdjacentInlines(tree);
|
|
6623
6860
|
}
|
|
@@ -7030,55 +7267,6 @@ var mentionsTitle = (opening, title) => {
|
|
|
7030
7267
|
};
|
|
7031
7268
|
var normalizeForCompare = (value) => value.replace(/\s+/g, " ").trim().toLowerCase();
|
|
7032
7269
|
|
|
7033
|
-
// src/detect-client-shell.ts
|
|
7034
|
-
var SHELL_SUSPICION_THRESHOLD = 300;
|
|
7035
|
-
var SHELL_EMPTY_THRESHOLD = 50;
|
|
7036
|
-
var BODY_PATTERN = /<body[^>]*>([\s\S]*?)<\/body>/i;
|
|
7037
|
-
var HEAD_PATTERN = /<head[^>]*>[\s\S]*?<\/head>/gi;
|
|
7038
|
-
var NOSCRIPT_PATTERN = /<noscript[^>]*>([\s\S]*?)<\/noscript>/gi;
|
|
7039
|
-
var INVISIBLE_ELEMENT_PATTERN = /<(script|style|template|svg|noscript)[^>]*>[\s\S]*?<\/\1>/gi;
|
|
7040
|
-
var COMMENT_PATTERN = /<!--[\s\S]*?-->/g;
|
|
7041
|
-
var TAG_PATTERN = /<[^>]+>/g;
|
|
7042
|
-
var MOUNT_POINT_PATTERN = /<[a-z][^>]*\sid=["'](?:root|app|__next|___gatsby|__nuxt|q-app|svelte|react-root)["']|<app-root[\s>]|\sdata-reactroot[\s>=]/i;
|
|
7043
|
-
var CHALLENGE_PATTERN = /cf-chl|challenge-platform|cf_chl_opt|just a moment|attention required|checking your browser|_incapsula_|px-captcha|datadome/i;
|
|
7044
|
-
var ENTITIES = {
|
|
7045
|
-
" ": " ",
|
|
7046
|
-
"&": "&",
|
|
7047
|
-
"<": "<",
|
|
7048
|
-
">": ">",
|
|
7049
|
-
""": '"',
|
|
7050
|
-
"'": "'"
|
|
7051
|
-
};
|
|
7052
|
-
var visibleTextOf = (markup) => markup.replace(COMMENT_PATTERN, " ").replace(INVISIBLE_ELEMENT_PATTERN, " ").replace(TAG_PATTERN, " ").replace(/&(?:nbsp|amp|lt|gt|quot|#39);/g, (entity) => ENTITIES[entity] ?? " ").replace(/\s+/g, " ").trim();
|
|
7053
|
-
var noscriptMentionsJavascript = (html) => {
|
|
7054
|
-
for (const match of html.matchAll(NOSCRIPT_PATTERN)) {
|
|
7055
|
-
if (/javascript/i.test(match[1] ?? "")) {
|
|
7056
|
-
return true;
|
|
7057
|
-
}
|
|
7058
|
-
}
|
|
7059
|
-
return false;
|
|
7060
|
-
};
|
|
7061
|
-
var detectClientShell = (html) => {
|
|
7062
|
-
const body = BODY_PATTERN.exec(html)?.[1] ?? html.replace(HEAD_PATTERN, " ");
|
|
7063
|
-
const visibleTextLength = visibleTextOf(body).length;
|
|
7064
|
-
if (visibleTextLength >= SHELL_SUSPICION_THRESHOLD) {
|
|
7065
|
-
return { isShell: false, visibleTextLength };
|
|
7066
|
-
}
|
|
7067
|
-
if (CHALLENGE_PATTERN.test(html)) {
|
|
7068
|
-
return { isShell: true, reason: "anti-bot-challenge", visibleTextLength };
|
|
7069
|
-
}
|
|
7070
|
-
if (MOUNT_POINT_PATTERN.test(body)) {
|
|
7071
|
-
return { isShell: true, reason: "spa-shell", visibleTextLength };
|
|
7072
|
-
}
|
|
7073
|
-
if (noscriptMentionsJavascript(html)) {
|
|
7074
|
-
return { isShell: true, reason: "noscript-only", visibleTextLength };
|
|
7075
|
-
}
|
|
7076
|
-
if (visibleTextLength < SHELL_EMPTY_THRESHOLD) {
|
|
7077
|
-
return { isShell: true, reason: "empty-body", visibleTextLength };
|
|
7078
|
-
}
|
|
7079
|
-
return { isShell: false, visibleTextLength };
|
|
7080
|
-
};
|
|
7081
|
-
|
|
7082
7270
|
// src/extract-meta-refresh.ts
|
|
7083
7271
|
var MAX_META_REFRESH_DELAY_SECONDS = 10;
|
|
7084
7272
|
var META_TAG_PATTERN = /<meta\b[^>]*>/gi;
|