@detiq/crawler 1.1.9 → 1.1.11

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 (83) hide show
  1. package/dist/accessibility-auditor.js +54 -74
  2. package/dist/ai-login.js +227 -249
  3. package/dist/auth-token-rotator.js +57 -67
  4. package/dist/auth.js +299 -336
  5. package/dist/browser-extension.js +104 -142
  6. package/dist/captcha-solver.js +98 -125
  7. package/dist/color-contrast.js +77 -89
  8. package/dist/content-chunker.js +126 -126
  9. package/dist/content-dedup.js +54 -71
  10. package/dist/cookie-consent.js +68 -83
  11. package/dist/cookie-jar.js +53 -60
  12. package/dist/crawler.js +4494 -5353
  13. package/dist/custom-js-injector.js +45 -61
  14. package/dist/dark-mode-capture.js +67 -81
  15. package/dist/dead-link-detector.js +47 -55
  16. package/dist/element-hash.js +8 -12
  17. package/dist/email-otp.js +154 -195
  18. package/dist/engine-router.js +87 -98
  19. package/dist/export/bulk-export.js +70 -55
  20. package/dist/export/vector-export.js +77 -105
  21. package/dist/extractors/advanced.js +249 -266
  22. package/dist/extractors/artifact-envelope.js +59 -58
  23. package/dist/failure-analysis-agent.js +37 -33
  24. package/dist/feature-flag-detector.js +51 -66
  25. package/dist/form-detector.js +55 -75
  26. package/dist/frontier.js +76 -101
  27. package/dist/gap-pass.js +68 -89
  28. package/dist/graphql-detector.js +62 -72
  29. package/dist/http-engine.js +282 -330
  30. package/dist/incremental-diff.js +54 -87
  31. package/dist/index.js +67 -28
  32. package/dist/input-fuzzer.js +93 -97
  33. package/dist/interactive-probe.js +715 -827
  34. package/dist/job-queue.js +2151 -2169
  35. package/dist/js-coverage.js +52 -56
  36. package/dist/js-router-detector.js +86 -117
  37. package/dist/language-detector.js +51 -63
  38. package/dist/link-classifier.js +32 -50
  39. package/dist/live-view.js +91 -108
  40. package/dist/locator-extractor.js +151 -187
  41. package/dist/logger.js +9 -7
  42. package/dist/markdown-extract.js +48 -61
  43. package/dist/native-crawler.js +780 -946
  44. package/dist/network-observer.js +135 -156
  45. package/dist/network-throttle.js +29 -40
  46. package/dist/openapi-generator.js +77 -92
  47. package/dist/pdf-extractor.js +72 -90
  48. package/dist/performance-metrics.js +80 -102
  49. package/dist/phash.js +125 -144
  50. package/dist/pii-scrubber.js +65 -68
  51. package/dist/playwright-recorder.js +70 -58
  52. package/dist/policies/crawl-safety.js +31 -34
  53. package/dist/profiles/domain-profile.js +130 -125
  54. package/dist/proxy.js +146 -172
  55. package/dist/rate-limiter.js +58 -65
  56. package/dist/redirect-tracker.js +60 -64
  57. package/dist/redis-frontier.js +116 -136
  58. package/dist/redis-queue.js +157 -144
  59. package/dist/response-cache.js +127 -140
  60. package/dist/responsive-detector.js +42 -46
  61. package/dist/retry-policy.js +46 -50
  62. package/dist/robots-txt.js +69 -83
  63. package/dist/screen-reader.js +45 -70
  64. package/dist/screenshot-masker.js +59 -77
  65. package/dist/seo-checker.js +88 -107
  66. package/dist/session-capture.js +192 -212
  67. package/dist/session-export.js +107 -133
  68. package/dist/sitemap-writer.js +38 -41
  69. package/dist/sitemap.js +141 -202
  70. package/dist/skyvern.js +123 -148
  71. package/dist/sqs-queue.js +42 -42
  72. package/dist/sso-token.js +74 -90
  73. package/dist/stealth/human-behavior.js +156 -173
  74. package/dist/telemetry.js +68 -82
  75. package/dist/test-executor.js +309 -335
  76. package/dist/tls-fingerprint.js +77 -88
  77. package/dist/url-pattern-completer.js +71 -90
  78. package/dist/visual-diff-verdict.js +23 -38
  79. package/dist/webhook-notifier.js +71 -69
  80. package/dist/websocket-detector.js +56 -68
  81. package/dist/worker.js +29 -38
  82. package/package.json +1 -1
  83. package/src/job-queue.ts +136 -0
@@ -1,282 +1,265 @@
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' };
12
- }
13
- catch (e) {
14
- return { matches: [], count: 0, method: 'css-selector-error' };
15
- }
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
+ }
16
14
  }
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?.();
28
- }
29
- return { matches, count: matches.length, method: 'xpath' };
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?.();
30
25
  }
31
- catch (e) {
32
- return { matches: [], count: 0, method: 'xpath-error' };
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;
33
39
  }
40
+ return { matches, count: matches.length, method: "regex" };
41
+ } catch {
42
+ return { matches: [], count: 0, method: "regex-error" };
43
+ }
34
44
  }
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;
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}`));
44
62
  }
45
- return { matches, count: matches.length, method: 'regex' };
46
- }
47
- catch {
48
- return { matches: [], count: 0, method: 'regex-error' };
49
- }
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
+ }
50
75
  }
51
- export function extractTables(html) {
52
- try {
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);
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;
80
127
  }
81
- catch {
82
- return [];
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 });
83
137
  }
84
- }
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;
164
- }
165
- export function extractStructuredProduct(jsonLd) {
166
- for (const item of jsonLd) {
167
- try {
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 */ }
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
+ }
204
147
  }
205
- return null;
148
+ } catch {
149
+ }
150
+ return meta;
206
151
  }
207
- export function extractImages(html, baseUrl) {
152
+ function extractStructuredProduct(jsonLd) {
153
+ for (const item of jsonLd) {
208
154
  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 [];
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 {
232
185
  }
186
+ }
187
+ return null;
233
188
  }
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
- }
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
+ }
210
+ }
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;
240
+ 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 {
276
247
  }
277
- return results;
278
- }
279
- catch {
280
- return [];
248
+ }
281
249
  }
250
+ return results;
251
+ } catch {
252
+ return [];
253
+ }
282
254
  }
255
+ export {
256
+ extractByCssSelector,
257
+ extractByRegex,
258
+ extractByXPath,
259
+ extractImages,
260
+ extractMediaFiles,
261
+ extractMetadata,
262
+ extractStructuredProduct,
263
+ extractTables
264
+ };
265
+ //# sourceMappingURL=advanced.js.map