@detiq/crawler 1.1.8 → 1.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/accessibility-auditor.js +74 -54
  2. package/dist/ai-login.js +249 -227
  3. package/dist/auth-token-rotator.js +67 -57
  4. package/dist/auth.js +336 -299
  5. package/dist/browser-extension.js +142 -104
  6. package/dist/captcha-solver.js +125 -98
  7. package/dist/color-contrast.js +89 -77
  8. package/dist/content-chunker.js +126 -126
  9. package/dist/content-dedup.js +71 -54
  10. package/dist/cookie-consent.js +83 -68
  11. package/dist/cookie-jar.js +60 -53
  12. package/dist/crawler.js +5345 -4486
  13. package/dist/custom-js-injector.js +61 -45
  14. package/dist/dark-mode-capture.js +81 -67
  15. package/dist/dead-link-detector.js +55 -47
  16. package/dist/element-hash.js +12 -8
  17. package/dist/email-otp.js +195 -154
  18. package/dist/engine-router.js +98 -87
  19. package/dist/export/bulk-export.js +55 -70
  20. package/dist/export/vector-export.js +105 -77
  21. package/dist/extractors/advanced.js +266 -249
  22. package/dist/extractors/artifact-envelope.js +58 -59
  23. package/dist/failure-analysis-agent.js +33 -37
  24. package/dist/feature-flag-detector.js +66 -51
  25. package/dist/form-detector.js +75 -55
  26. package/dist/frontier.js +101 -76
  27. package/dist/gap-pass.js +89 -68
  28. package/dist/graphql-detector.js +72 -62
  29. package/dist/http-engine.js +330 -282
  30. package/dist/incremental-diff.js +87 -54
  31. package/dist/index.js +28 -67
  32. package/dist/input-fuzzer.js +97 -93
  33. package/dist/interactive-probe.js +827 -715
  34. package/dist/job-queue.js +2169 -2151
  35. package/dist/js-coverage.js +56 -52
  36. package/dist/js-router-detector.js +117 -86
  37. package/dist/language-detector.js +63 -51
  38. package/dist/link-classifier.js +50 -32
  39. package/dist/live-view.js +108 -91
  40. package/dist/locator-extractor.js +187 -151
  41. package/dist/logger.js +7 -9
  42. package/dist/markdown-extract.js +61 -48
  43. package/dist/native-crawler.js +946 -780
  44. package/dist/network-observer.js +156 -135
  45. package/dist/network-throttle.js +40 -29
  46. package/dist/openapi-generator.js +92 -77
  47. package/dist/pdf-extractor.js +90 -72
  48. package/dist/performance-metrics.js +102 -80
  49. package/dist/phash.js +144 -125
  50. package/dist/pii-scrubber.js +68 -65
  51. package/dist/playwright-recorder.js +58 -70
  52. package/dist/policies/crawl-safety.js +34 -31
  53. package/dist/profiles/domain-profile.js +125 -130
  54. package/dist/proxy.js +172 -146
  55. package/dist/rate-limiter.js +65 -58
  56. package/dist/redirect-tracker.js +64 -60
  57. package/dist/redis-frontier.js +136 -116
  58. package/dist/redis-queue.js +144 -157
  59. package/dist/response-cache.js +140 -127
  60. package/dist/responsive-detector.js +46 -42
  61. package/dist/retry-policy.js +50 -46
  62. package/dist/robots-txt.js +83 -69
  63. package/dist/screen-reader.js +70 -45
  64. package/dist/screenshot-masker.js +77 -59
  65. package/dist/seo-checker.js +107 -88
  66. package/dist/session-capture.js +212 -192
  67. package/dist/session-export.js +133 -107
  68. package/dist/sitemap-writer.js +41 -38
  69. package/dist/sitemap.js +202 -141
  70. package/dist/skyvern.js +148 -123
  71. package/dist/sqs-queue.js +42 -42
  72. package/dist/sso-token.js +90 -74
  73. package/dist/stealth/human-behavior.js +173 -156
  74. package/dist/telemetry.js +82 -68
  75. package/dist/test-executor.js +335 -309
  76. package/dist/tls-fingerprint.js +88 -77
  77. package/dist/url-pattern-completer.js +90 -71
  78. package/dist/visual-diff-verdict.js +38 -23
  79. package/dist/webhook-notifier.js +69 -71
  80. package/dist/websocket-detector.js +68 -56
  81. package/dist/worker.js +38 -29
  82. package/package.json +1 -1
@@ -1,265 +1,282 @@
1
- import { parseHTML } from "linkedom";
2
- function extractByCssSelector(html, selector, attr) {
3
- try {
4
- const { document } = parseHTML(html);
5
- const nodes = Array.from(document.querySelectorAll(selector));
6
- const matches = nodes.map((n) => {
7
- if (attr) return n.getAttribute(attr) ?? "";
8
- return n.textContent?.trim() ?? "";
9
- }).filter(Boolean);
10
- return { matches, count: matches.length, method: "css-selector" };
11
- } catch (e) {
12
- return { matches: [], count: 0, method: "css-selector-error" };
13
- }
14
- }
15
- function extractByXPath(html, xpath) {
16
- try {
17
- const { document, XPathResult } = parseHTML(html);
18
- const result = document.evaluate(xpath, document, null, XPathResult?.ANY_TYPE ?? 0, null);
19
- const matches = [];
20
- let node = result.iterateNext?.();
21
- while (node) {
22
- const text = node.textContent?.trim() ?? node.nodeValue?.trim() ?? "";
23
- if (text) matches.push(text);
24
- node = result.iterateNext?.();
1
+ import { parseHTML } from 'linkedom';
2
+ export function extractByCssSelector(html, selector, attr) {
3
+ try {
4
+ const { document } = parseHTML(html);
5
+ const nodes = Array.from(document.querySelectorAll(selector));
6
+ const matches = nodes.map((n) => {
7
+ if (attr)
8
+ return n.getAttribute(attr) ?? '';
9
+ return n.textContent?.trim() ?? '';
10
+ }).filter(Boolean);
11
+ return { matches, count: matches.length, method: 'css-selector' };
25
12
  }
26
- return { matches, count: matches.length, method: "xpath" };
27
- } catch (e) {
28
- return { matches: [], count: 0, method: "xpath-error" };
29
- }
30
- }
31
- function extractByRegex(text, pattern, flags) {
32
- try {
33
- const re = new RegExp(pattern, (flags ?? "g").includes("g") ? flags : (flags ?? "") + "g");
34
- const matches = [];
35
- let m;
36
- while ((m = re.exec(text)) !== null) {
37
- matches.push(m[1] ?? m[0]);
38
- if (!re.global) break;
13
+ catch (e) {
14
+ return { matches: [], count: 0, method: 'css-selector-error' };
39
15
  }
40
- return { matches, count: matches.length, method: "regex" };
41
- } catch {
42
- return { matches: [], count: 0, method: "regex-error" };
43
- }
44
16
  }
45
- function extractTables(html) {
46
- try {
47
- const { document } = parseHTML(html);
48
- const tables = Array.from(document.querySelectorAll("table"));
49
- return tables.map((table) => {
50
- const headers = [];
51
- const rows = [];
52
- const ths = [...table.querySelectorAll("thead th, thead td, tr:first-child th")];
53
- if (ths.length > 0) {
54
- ths.forEach((th, i) => headers.push(th.textContent?.trim() || `col${i + 1}`));
55
- }
56
- const trs = [...table.querySelectorAll("tbody tr, tr")];
57
- for (const tr of trs) {
58
- const cells = [...tr.querySelectorAll("td, th")];
59
- if (cells.length === 0) continue;
60
- if (headers.length === 0) {
61
- cells.forEach((_, i) => headers.push(`col${i + 1}`));
17
+ export function extractByXPath(html, xpath) {
18
+ try {
19
+ const { document, XPathResult } = parseHTML(html);
20
+ const result = document.evaluate(xpath, document, null, XPathResult?.ANY_TYPE ?? 0, null);
21
+ const matches = [];
22
+ let node = result.iterateNext?.();
23
+ while (node) {
24
+ const text = node.textContent?.trim() ?? node.nodeValue?.trim() ?? '';
25
+ if (text)
26
+ matches.push(text);
27
+ node = result.iterateNext?.();
62
28
  }
63
- const row = {};
64
- cells.forEach((cell, i) => {
65
- const key = headers[i] || `col${i + 1}`;
66
- row[key] = cell.textContent?.trim() ?? "";
67
- });
68
- if (Object.values(row).some((v) => v)) rows.push(row);
69
- }
70
- return rows;
71
- }).filter((t) => t.length > 0);
72
- } catch {
73
- return [];
74
- }
75
- }
76
- function extractMetadata(html, url) {
77
- const meta = {
78
- title: null,
79
- description: null,
80
- keywords: null,
81
- ogTitle: null,
82
- ogDescription: null,
83
- ogImage: null,
84
- ogUrl: null,
85
- ogType: null,
86
- ogSiteName: null,
87
- ogLocale: null,
88
- twitterCard: null,
89
- twitterTitle: null,
90
- twitterDescription: null,
91
- twitterImage: null,
92
- twitterSite: null,
93
- canonical: null,
94
- robots: null,
95
- hreflang: [],
96
- themeColor: null,
97
- favicon: null,
98
- jsonLd: [],
99
- schemaTypes: [],
100
- lang: null
101
- };
102
- try {
103
- const { document } = parseHTML(html);
104
- meta.title = document.title ?? document.querySelector("title")?.textContent?.trim() ?? null;
105
- meta.lang = document.documentElement?.getAttribute("lang") ?? null;
106
- const metas = [...document.querySelectorAll("meta")];
107
- for (const m of metas) {
108
- const name = m.getAttribute("name")?.toLowerCase() ?? "";
109
- const prop = m.getAttribute("property")?.toLowerCase() ?? "";
110
- const content = m.getAttribute("content") ?? "";
111
- if (name === "description") meta.description = content;
112
- if (name === "keywords") meta.keywords = content;
113
- if (name === "robots") meta.robots = content;
114
- if (name === "twitter:card") meta.twitterCard = content;
115
- if (name === "twitter:title") meta.twitterTitle = content;
116
- if (name === "twitter:description") meta.twitterDescription = content;
117
- if (name === "twitter:image") meta.twitterImage = content;
118
- if (name === "twitter:site") meta.twitterSite = content;
119
- if (name === "theme-color") meta.themeColor = content;
120
- if (prop === "og:title") meta.ogTitle = content;
121
- if (prop === "og:description") meta.ogDescription = content;
122
- if (prop === "og:image") meta.ogImage = content;
123
- if (prop === "og:url") meta.ogUrl = content;
124
- if (prop === "og:type") meta.ogType = content;
125
- if (prop === "og:site_name") meta.ogSiteName = content;
126
- if (prop === "og:locale") meta.ogLocale = content;
29
+ return { matches, count: matches.length, method: 'xpath' };
127
30
  }
128
- const canonical = document.querySelector('link[rel="canonical"]');
129
- if (canonical) meta.canonical = canonical.getAttribute("href") ?? null;
130
- const faviconEl = document.querySelector('link[rel="icon"], link[rel="shortcut icon"]');
131
- if (faviconEl) meta.favicon = faviconEl.getAttribute("href") ?? null;
132
- const hreflangEls = [...document.querySelectorAll('link[rel="alternate"][hreflang]')];
133
- for (const el of hreflangEls) {
134
- const lang = el.getAttribute("hreflang") ?? "";
135
- const href = el.getAttribute("href") ?? "";
136
- if (lang && href) meta.hreflang.push({ lang, href });
31
+ catch (e) {
32
+ return { matches: [], count: 0, method: 'xpath-error' };
137
33
  }
138
- const jsonLdScripts = [...document.querySelectorAll('script[type="application/ld+json"]')];
139
- for (const s of jsonLdScripts) {
140
- try {
141
- const parsed = JSON.parse(s.textContent ?? "{}");
142
- meta.jsonLd.push(parsed);
143
- const types = Array.isArray(parsed) ? parsed.map((p) => p["@type"]).flat() : [parsed["@type"]];
144
- meta.schemaTypes.push(...types.filter(Boolean));
145
- } catch {
146
- }
34
+ }
35
+ export function extractByRegex(text, pattern, flags) {
36
+ try {
37
+ const re = new RegExp(pattern, (flags ?? 'g').includes('g') ? flags : (flags ?? '') + 'g');
38
+ const matches = [];
39
+ let m;
40
+ while ((m = re.exec(text)) !== null) {
41
+ matches.push(m[1] ?? m[0]);
42
+ if (!re.global)
43
+ break;
44
+ }
45
+ return { matches, count: matches.length, method: 'regex' };
46
+ }
47
+ catch {
48
+ return { matches: [], count: 0, method: 'regex-error' };
147
49
  }
148
- } catch {
149
- }
150
- return meta;
151
50
  }
152
- function extractStructuredProduct(jsonLd) {
153
- for (const item of jsonLd) {
51
+ export function extractTables(html) {
154
52
  try {
155
- const type = item["@type"];
156
- const isProduct = Array.isArray(type) ? type.some((t) => typeof t === "string" && t.toLowerCase() === "product") : typeof type === "string" && type.toLowerCase() === "product";
157
- if (!isProduct) continue;
158
- const i = item;
159
- const offers = Array.isArray(i.offers) ? i.offers[0] : i.offers;
160
- const rawPrice = offers?.price;
161
- const price = rawPrice != null ? parseFloat(rawPrice) : NaN;
162
- const rawRating = i.aggregateRating?.ratingValue;
163
- const rating = rawRating != null ? parseFloat(rawRating) : NaN;
164
- const rawReviewCount = i.aggregateRating?.reviewCount;
165
- const reviewCount = rawReviewCount != null ? parseInt(rawReviewCount, 10) : NaN;
166
- const rawAvailability = offers?.availability;
167
- const availability = rawAvailability ? rawAvailability.replace(/^https?:\/\/schema\.org\//, "") : null;
168
- const rawImage = i.image;
169
- const image = Array.isArray(rawImage) ? rawImage[0] ?? null : rawImage ?? null;
170
- const brand = i.brand?.name ?? i.brand ?? null;
171
- return {
172
- name: i.name ?? null,
173
- description: i.description ?? null,
174
- brand: typeof brand === "string" ? brand : null,
175
- sku: i.sku ?? null,
176
- price: isNaN(price) ? null : price,
177
- priceCurrency: offers?.priceCurrency ?? null,
178
- availability,
179
- rating: isNaN(rating) ? null : rating,
180
- reviewCount: isNaN(reviewCount) ? null : reviewCount,
181
- image,
182
- url: i.url ?? null
183
- };
184
- } catch {
53
+ const { document } = parseHTML(html);
54
+ const tables = Array.from(document.querySelectorAll('table'));
55
+ return tables.map((table) => {
56
+ const headers = [];
57
+ const rows = [];
58
+ const ths = [...table.querySelectorAll('thead th, thead td, tr:first-child th')];
59
+ if (ths.length > 0) {
60
+ ths.forEach((th, i) => headers.push(th.textContent?.trim() || `col${i + 1}`));
61
+ }
62
+ const trs = [...table.querySelectorAll('tbody tr, tr')];
63
+ for (const tr of trs) {
64
+ const cells = [...tr.querySelectorAll('td, th')];
65
+ if (cells.length === 0)
66
+ continue;
67
+ if (headers.length === 0) {
68
+ cells.forEach((_, i) => headers.push(`col${i + 1}`));
69
+ }
70
+ const row = {};
71
+ cells.forEach((cell, i) => {
72
+ const key = headers[i] || `col${i + 1}`;
73
+ row[key] = cell.textContent?.trim() ?? '';
74
+ });
75
+ if (Object.values(row).some(v => v))
76
+ rows.push(row);
77
+ }
78
+ return rows;
79
+ }).filter(t => t.length > 0);
80
+ }
81
+ catch {
82
+ return [];
185
83
  }
186
- }
187
- return null;
188
84
  }
189
- function extractImages(html, baseUrl) {
190
- try {
191
- const { document } = parseHTML(html);
192
- const base = new URL(baseUrl);
193
- return Array.from(document.querySelectorAll("img")).map((img) => {
194
- const src = img.getAttribute("src") ?? img.getAttribute("data-src") ?? "";
195
- if (!src) return null;
196
- try {
197
- return {
198
- src: new URL(src, base).href,
199
- alt: img.getAttribute("alt") ?? "",
200
- width: img.getAttribute("width") ? parseInt(img.getAttribute("width")) : void 0,
201
- height: img.getAttribute("height") ? parseInt(img.getAttribute("height")) : void 0
202
- };
203
- } catch {
204
- return null;
205
- }
206
- }).filter((x) => x !== null);
207
- } catch {
208
- return [];
209
- }
85
+ export function extractMetadata(html, url) {
86
+ const meta = {
87
+ title: null, description: null, keywords: null,
88
+ ogTitle: null, ogDescription: null, ogImage: null, ogUrl: null, ogType: null,
89
+ ogSiteName: null, ogLocale: null,
90
+ twitterCard: null, twitterTitle: null, twitterDescription: null,
91
+ twitterImage: null, twitterSite: null,
92
+ canonical: null, robots: null,
93
+ hreflang: [], themeColor: null, favicon: null,
94
+ jsonLd: [], schemaTypes: [], lang: null,
95
+ };
96
+ try {
97
+ const { document } = parseHTML(html);
98
+ meta.title = document.title ?? document.querySelector('title')?.textContent?.trim() ?? null;
99
+ meta.lang = document.documentElement?.getAttribute('lang') ?? null;
100
+ const metas = [...document.querySelectorAll('meta')];
101
+ for (const m of metas) {
102
+ const name = m.getAttribute('name')?.toLowerCase() ?? '';
103
+ const prop = m.getAttribute('property')?.toLowerCase() ?? '';
104
+ const content = m.getAttribute('content') ?? '';
105
+ if (name === 'description')
106
+ meta.description = content;
107
+ if (name === 'keywords')
108
+ meta.keywords = content;
109
+ if (name === 'robots')
110
+ meta.robots = content;
111
+ if (name === 'twitter:card')
112
+ meta.twitterCard = content;
113
+ if (name === 'twitter:title')
114
+ meta.twitterTitle = content;
115
+ if (name === 'twitter:description')
116
+ meta.twitterDescription = content;
117
+ if (name === 'twitter:image')
118
+ meta.twitterImage = content;
119
+ if (name === 'twitter:site')
120
+ meta.twitterSite = content;
121
+ if (name === 'theme-color')
122
+ meta.themeColor = content;
123
+ if (prop === 'og:title')
124
+ meta.ogTitle = content;
125
+ if (prop === 'og:description')
126
+ meta.ogDescription = content;
127
+ if (prop === 'og:image')
128
+ meta.ogImage = content;
129
+ if (prop === 'og:url')
130
+ meta.ogUrl = content;
131
+ if (prop === 'og:type')
132
+ meta.ogType = content;
133
+ if (prop === 'og:site_name')
134
+ meta.ogSiteName = content;
135
+ if (prop === 'og:locale')
136
+ meta.ogLocale = content;
137
+ }
138
+ const canonical = document.querySelector('link[rel="canonical"]');
139
+ if (canonical)
140
+ meta.canonical = canonical.getAttribute('href') ?? null;
141
+ const faviconEl = document.querySelector('link[rel="icon"], link[rel="shortcut icon"]');
142
+ if (faviconEl)
143
+ meta.favicon = faviconEl.getAttribute('href') ?? null;
144
+ const hreflangEls = [...document.querySelectorAll('link[rel="alternate"][hreflang]')];
145
+ for (const el of hreflangEls) {
146
+ const lang = el.getAttribute('hreflang') ?? '';
147
+ const href = el.getAttribute('href') ?? '';
148
+ if (lang && href)
149
+ meta.hreflang.push({ lang, href });
150
+ }
151
+ const jsonLdScripts = [...document.querySelectorAll('script[type="application/ld+json"]')];
152
+ for (const s of jsonLdScripts) {
153
+ try {
154
+ const parsed = JSON.parse(s.textContent ?? '{}');
155
+ meta.jsonLd.push(parsed);
156
+ const types = Array.isArray(parsed) ? parsed.map((p) => p['@type']).flat() : [parsed['@type']];
157
+ meta.schemaTypes.push(...types.filter(Boolean));
158
+ }
159
+ catch { /* skip invalid JSON-LD */ }
160
+ }
161
+ }
162
+ catch { /* non-fatal */ }
163
+ return meta;
210
164
  }
211
- function extractMediaFiles(html, baseUrl) {
212
- const IMAGE_EXT = /\.(png|jpe?g|gif|webp|svg|ico|avif)(\?.*)?$/i;
213
- const VIDEO_EXT = /\.(mp4|webm|ogv|mov|avi)(\?.*)?$/i;
214
- const AUDIO_EXT = /\.(mp3|ogg|wav|flac|aac)(\?.*)?$/i;
215
- const DOC_EXT = /\.(pdf|docx?|xlsx?|pptx?|csv|zip)(\?.*)?$/i;
216
- function classify(url, tag) {
217
- if (tag === "img" || IMAGE_EXT.test(url)) return "image";
218
- if (tag === "video" || VIDEO_EXT.test(url)) return "video";
219
- if (tag === "audio" || AUDIO_EXT.test(url)) return "audio";
220
- if (DOC_EXT.test(url)) return "document";
221
- return "document";
222
- }
223
- try {
224
- const { document } = parseHTML(html);
225
- const base = new URL(baseUrl);
226
- const results = [];
227
- const seen = /* @__PURE__ */ new Set();
228
- const selectors = ["img[src],video[src],audio[src],source[src]", "src"];
229
- const attrPairs = [
230
- ["img", "src"],
231
- ["video", "src"],
232
- ["audio", "src"],
233
- ["source", "src"],
234
- ["a", "href"]
235
- ];
236
- for (const [sel, attr] of attrPairs) {
237
- for (const el of [...document.querySelectorAll(sel)]) {
238
- const raw = el.getAttribute(attr) ?? "";
239
- if (!raw) continue;
165
+ export function extractStructuredProduct(jsonLd) {
166
+ for (const item of jsonLd) {
240
167
  try {
241
- const resolved = new URL(raw, base).href;
242
- if (seen.has(resolved)) continue;
243
- if (sel === "a" && !DOC_EXT.test(resolved) && !IMAGE_EXT.test(resolved) && !VIDEO_EXT.test(resolved) && !AUDIO_EXT.test(resolved)) continue;
244
- seen.add(resolved);
245
- results.push({ url: resolved, type: classify(resolved, sel), tag: sel });
246
- } catch {
168
+ const type = item['@type'];
169
+ const isProduct = Array.isArray(type)
170
+ ? type.some((t) => typeof t === 'string' && t.toLowerCase() === 'product')
171
+ : typeof type === 'string' && type.toLowerCase() === 'product';
172
+ if (!isProduct)
173
+ continue;
174
+ const i = item;
175
+ const offers = Array.isArray(i.offers) ? i.offers[0] : i.offers;
176
+ const rawPrice = offers?.price;
177
+ const price = rawPrice != null ? parseFloat(rawPrice) : NaN;
178
+ const rawRating = i.aggregateRating?.ratingValue;
179
+ const rating = rawRating != null ? parseFloat(rawRating) : NaN;
180
+ const rawReviewCount = i.aggregateRating?.reviewCount;
181
+ const reviewCount = rawReviewCount != null ? parseInt(rawReviewCount, 10) : NaN;
182
+ const rawAvailability = offers?.availability;
183
+ const availability = rawAvailability
184
+ ? rawAvailability.replace(/^https?:\/\/schema\.org\//, '')
185
+ : null;
186
+ const rawImage = i.image;
187
+ const image = Array.isArray(rawImage) ? (rawImage[0] ?? null) : (rawImage ?? null);
188
+ const brand = i.brand?.name ?? i.brand ?? null;
189
+ return {
190
+ name: i.name ?? null,
191
+ description: i.description ?? null,
192
+ brand: typeof brand === 'string' ? brand : null,
193
+ sku: i.sku ?? null,
194
+ price: isNaN(price) ? null : price,
195
+ priceCurrency: offers?.priceCurrency ?? null,
196
+ availability,
197
+ rating: isNaN(rating) ? null : rating,
198
+ reviewCount: isNaN(reviewCount) ? null : reviewCount,
199
+ image,
200
+ url: i.url ?? null,
201
+ };
202
+ }
203
+ catch { /* skip malformed item */ }
204
+ }
205
+ return null;
206
+ }
207
+ export function extractImages(html, baseUrl) {
208
+ try {
209
+ const { document } = parseHTML(html);
210
+ const base = new URL(baseUrl);
211
+ return Array.from(document.querySelectorAll('img'))
212
+ .map((img) => {
213
+ const src = img.getAttribute('src') ?? img.getAttribute('data-src') ?? '';
214
+ if (!src)
215
+ return null;
216
+ try {
217
+ return {
218
+ src: new URL(src, base).href,
219
+ alt: (img.getAttribute('alt') ?? ''),
220
+ width: img.getAttribute('width') ? parseInt(img.getAttribute('width')) : undefined,
221
+ height: img.getAttribute('height') ? parseInt(img.getAttribute('height')) : undefined,
222
+ };
223
+ }
224
+ catch {
225
+ return null;
226
+ }
227
+ })
228
+ .filter((x) => x !== null);
229
+ }
230
+ catch {
231
+ return [];
232
+ }
233
+ }
234
+ export function extractMediaFiles(html, baseUrl) {
235
+ const IMAGE_EXT = /\.(png|jpe?g|gif|webp|svg|ico|avif)(\?.*)?$/i;
236
+ const VIDEO_EXT = /\.(mp4|webm|ogv|mov|avi)(\?.*)?$/i;
237
+ const AUDIO_EXT = /\.(mp3|ogg|wav|flac|aac)(\?.*)?$/i;
238
+ const DOC_EXT = /\.(pdf|docx?|xlsx?|pptx?|csv|zip)(\?.*)?$/i;
239
+ function classify(url, tag) {
240
+ if (tag === 'img' || IMAGE_EXT.test(url))
241
+ return 'image';
242
+ if (tag === 'video' || VIDEO_EXT.test(url))
243
+ return 'video';
244
+ if (tag === 'audio' || AUDIO_EXT.test(url))
245
+ return 'audio';
246
+ if (DOC_EXT.test(url))
247
+ return 'document';
248
+ return 'document';
249
+ }
250
+ try {
251
+ const { document } = parseHTML(html);
252
+ const base = new URL(baseUrl);
253
+ const results = [];
254
+ const seen = new Set();
255
+ const selectors = ['img[src],video[src],audio[src],source[src]', 'src'];
256
+ const attrPairs = [
257
+ ['img', 'src'], ['video', 'src'], ['audio', 'src'], ['source', 'src'],
258
+ ['a', 'href'],
259
+ ];
260
+ for (const [sel, attr] of attrPairs) {
261
+ for (const el of [...document.querySelectorAll(sel)]) {
262
+ const raw = el.getAttribute(attr) ?? '';
263
+ if (!raw)
264
+ continue;
265
+ try {
266
+ const resolved = new URL(raw, base).href;
267
+ if (seen.has(resolved))
268
+ continue;
269
+ if (sel === 'a' && !DOC_EXT.test(resolved) && !IMAGE_EXT.test(resolved) && !VIDEO_EXT.test(resolved) && !AUDIO_EXT.test(resolved))
270
+ continue;
271
+ seen.add(resolved);
272
+ results.push({ url: resolved, type: classify(resolved, sel), tag: sel });
273
+ }
274
+ catch { /* skip */ }
275
+ }
247
276
  }
248
- }
277
+ return results;
278
+ }
279
+ catch {
280
+ return [];
249
281
  }
250
- return results;
251
- } catch {
252
- return [];
253
- }
254
282
  }
255
- export {
256
- extractByCssSelector,
257
- extractByRegex,
258
- extractByXPath,
259
- extractImages,
260
- extractMediaFiles,
261
- extractMetadata,
262
- extractStructuredProduct,
263
- extractTables
264
- };
265
- //# sourceMappingURL=advanced.js.map
@@ -1,63 +1,62 @@
1
- import crypto from "node:crypto";
2
- function sha256(input) {
3
- return crypto.createHash("sha256").update(input).digest("hex");
1
+ import crypto from 'node:crypto';
2
+ export function sha256(input) {
3
+ return crypto.createHash('sha256').update(input).digest('hex');
4
4
  }
5
- function buildFitMarkdown(markdown, keywords = []) {
6
- const lines = markdown.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
7
- const lowerKeywords = keywords.map((k) => k.toLowerCase()).filter(Boolean);
8
- const filtered = lines.filter((line) => {
9
- const plain = line.replace(/[#*_`>\-[\]()]/g, "").trim();
10
- if (plain.length < 3) return false;
11
- if (/^(cookie|privacy|terms|copyright|all rights reserved|subscribe|newsletter)$/i.test(plain)) return false;
12
- if (lowerKeywords.length === 0) return true;
13
- const lower = plain.toLowerCase();
14
- return lowerKeywords.some((kw) => lower.includes(kw)) || /^#{1,4}\s/.test(line);
15
- });
16
- const candidate = filtered.join("\n");
17
- return candidate.length >= 200 ? candidate : markdown;
5
+ export function buildFitMarkdown(markdown, keywords = []) {
6
+ const lines = markdown
7
+ .split('\n')
8
+ .map((line) => line.trimEnd())
9
+ .filter((line) => line.trim().length > 0);
10
+ const lowerKeywords = keywords.map((k) => k.toLowerCase()).filter(Boolean);
11
+ const filtered = lines.filter((line) => {
12
+ const plain = line.replace(/[#*_`>\-[\]()]/g, '').trim();
13
+ if (plain.length < 3)
14
+ return false;
15
+ if (/^(cookie|privacy|terms|copyright|all rights reserved|subscribe|newsletter)$/i.test(plain))
16
+ return false;
17
+ if (lowerKeywords.length === 0)
18
+ return true;
19
+ const lower = plain.toLowerCase();
20
+ return lowerKeywords.some((kw) => lower.includes(kw)) || /^#{1,4}\s/.test(line);
21
+ });
22
+ const candidate = filtered.join('\n');
23
+ return candidate.length >= 200 ? candidate : markdown;
18
24
  }
19
- function summarizeStructuredElements(elements) {
20
- const forms = elements.filter((e) => ["email", "password", "text", "tel", "date", "number", "select", "textarea"].includes(e.role));
21
- const actions = elements.filter((e) => e.role === "button" || e.role === "submit" || e.role === "link" || e.role === "a");
22
- return {
23
- elementCount: elements.length,
24
- formFieldCount: forms.length,
25
- actionCount: actions.length,
26
- fields: forms.slice(0, 50).map((e) => ({ label: e.label, role: e.role, expectedData: e.expectedData })),
27
- actions: actions.slice(0, 50).map((e) => ({ label: e.label, role: e.role }))
28
- };
25
+ export function summarizeStructuredElements(elements) {
26
+ const forms = elements.filter((e) => ['email', 'password', 'text', 'tel', 'date', 'number', 'select', 'textarea'].includes(e.role));
27
+ const actions = elements.filter((e) => e.role === 'button' || e.role === 'submit' || e.role === 'link' || e.role === 'a');
28
+ return {
29
+ elementCount: elements.length,
30
+ formFieldCount: forms.length,
31
+ actionCount: actions.length,
32
+ fields: forms.slice(0, 50).map((e) => ({ label: e.label, role: e.role, expectedData: e.expectedData })),
33
+ actions: actions.slice(0, 50).map((e) => ({ label: e.label, role: e.role })),
34
+ };
29
35
  }
30
- function buildCrawlArtifactEnvelope(input) {
31
- const accessibilityTree = input.accessibilityElements ? JSON.stringify(input.accessibilityElements) : void 0;
32
- return {
33
- tenantId: input.tenantId,
34
- projectId: input.projectId,
35
- jobId: input.jobId,
36
- url: input.url,
37
- canonicalUrl: input.canonicalUrl,
38
- screenId: input.screenId,
39
- status: "ok",
40
- outputs: {
41
- screenshot: input.screenshotPath,
42
- html: input.domPath,
43
- markdown: input.markdown,
44
- fitMarkdown: input.fitMarkdown,
45
- accessibilityTree,
46
- structuredJson: input.structuredJson
47
- },
48
- fingerprints: {
49
- textHash: input.markdown ? sha256(input.markdown) : void 0,
50
- domHash: input.domContent ? sha256(input.domContent) : void 0,
51
- accessibilityHash: accessibilityTree ? sha256(accessibilityTree) : void 0,
52
- urlCanonicalHash: sha256(input.canonicalUrl)
53
- },
54
- evidence: input.evidence ?? []
55
- };
36
+ export function buildCrawlArtifactEnvelope(input) {
37
+ const accessibilityTree = input.accessibilityElements ? JSON.stringify(input.accessibilityElements) : undefined;
38
+ return {
39
+ tenantId: input.tenantId,
40
+ projectId: input.projectId,
41
+ jobId: input.jobId,
42
+ url: input.url,
43
+ canonicalUrl: input.canonicalUrl,
44
+ screenId: input.screenId,
45
+ status: 'ok',
46
+ outputs: {
47
+ screenshot: input.screenshotPath,
48
+ html: input.domPath,
49
+ markdown: input.markdown,
50
+ fitMarkdown: input.fitMarkdown,
51
+ accessibilityTree,
52
+ structuredJson: input.structuredJson,
53
+ },
54
+ fingerprints: {
55
+ textHash: input.markdown ? sha256(input.markdown) : undefined,
56
+ domHash: input.domContent ? sha256(input.domContent) : undefined,
57
+ accessibilityHash: accessibilityTree ? sha256(accessibilityTree) : undefined,
58
+ urlCanonicalHash: sha256(input.canonicalUrl),
59
+ },
60
+ evidence: input.evidence ?? [],
61
+ };
56
62
  }
57
- export {
58
- buildCrawlArtifactEnvelope,
59
- buildFitMarkdown,
60
- sha256,
61
- summarizeStructuredElements
62
- };
63
- //# sourceMappingURL=artifact-envelope.js.map