mcp-scraper 0.51.2 → 0.52.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/README.md +3 -3
- package/dist/bin/api-server.cjs +2819 -869
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +5 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +3 -3
- package/dist/bin/mcp-scraper-install.cjs +1 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +1 -1
- package/dist/bin/mcp-stdio-server.cjs +355 -28
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/bin/paa-harvest.cjs +4 -0
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +2 -2
- package/dist/{chunk-ZSJHRZY5.js → chunk-J7TH5KU7.js} +2 -2
- package/dist/{chunk-FCZ3QCZP.js → chunk-OT2AF7SH.js} +356 -29
- package/dist/chunk-OT2AF7SH.js.map +1 -0
- package/dist/chunk-SG2PEHR3.js +562 -0
- package/dist/chunk-SG2PEHR3.js.map +1 -0
- package/dist/{chunk-GOZIG6HD.js → chunk-TVZD37SE.js} +2 -2
- package/dist/{chunk-3ZUBQQPQ.js → chunk-V7CEOUBF.js} +5 -1
- package/dist/chunk-V7CEOUBF.js.map +1 -0
- package/dist/chunk-VIAXQTZK.js +7 -0
- package/dist/chunk-VIAXQTZK.js.map +1 -0
- package/dist/{extract-bundle-RCTNANCH.js → extract-bundle-GUEUCTAE.js} +2 -2
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{server-KAEHVDY7.js → server-7EXEDKAU.js} +1857 -401
- package/dist/server-7EXEDKAU.js.map +1 -0
- package/dist/{worker-KQN673JF.js → worker-JL4TG6IF.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-27FMOD6S.js +0 -430
- package/dist/chunk-27FMOD6S.js.map +0 -1
- package/dist/chunk-3ZUBQQPQ.js.map +0 -1
- package/dist/chunk-FCZ3QCZP.js.map +0 -1
- package/dist/chunk-JNRSR5ZJ.js +0 -7
- package/dist/chunk-JNRSR5ZJ.js.map +0 -1
- package/dist/server-KAEHVDY7.js.map +0 -1
- /package/dist/{chunk-ZSJHRZY5.js.map → chunk-J7TH5KU7.js.map} +0 -0
- /package/dist/{chunk-GOZIG6HD.js.map → chunk-TVZD37SE.js.map} +0 -0
- /package/dist/{extract-bundle-RCTNANCH.js.map → extract-bundle-GUEUCTAE.js.map} +0 -0
- /package/dist/{worker-KQN673JF.js.map → worker-JL4TG6IF.js.map} +0 -0
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createPrivateArtifactFromStream,
|
|
3
|
+
privateArtifactOwnerId,
|
|
4
|
+
readPrivateArtifactBuffer,
|
|
5
|
+
renewPrivateArtifactDownload,
|
|
6
|
+
validatePublicHttpUrl
|
|
7
|
+
} from "./chunk-2K74LVV7.js";
|
|
8
|
+
|
|
9
|
+
// src/lib/media-extractor.ts
|
|
10
|
+
import { createWriteStream, mkdirSync, rmSync } from "fs";
|
|
11
|
+
import { homedir } from "os";
|
|
12
|
+
import { join, extname, basename } from "path";
|
|
13
|
+
import { pipeline } from "stream/promises";
|
|
14
|
+
import { Readable, Transform } from "stream";
|
|
15
|
+
var AD_PATTERNS = [
|
|
16
|
+
"doubleclick.net",
|
|
17
|
+
"googlesyndication.com",
|
|
18
|
+
"googletagmanager.com",
|
|
19
|
+
"google-analytics.com",
|
|
20
|
+
"googletagservices.com",
|
|
21
|
+
"adservice.google",
|
|
22
|
+
"googletag.",
|
|
23
|
+
"pagead2.googlesyndication",
|
|
24
|
+
"facebook.net/tr",
|
|
25
|
+
"connect.facebook.net",
|
|
26
|
+
"fbcdn.net/rsrc",
|
|
27
|
+
"analytics.twitter.com",
|
|
28
|
+
"static.ads-twitter.com",
|
|
29
|
+
"ads.twitter.com",
|
|
30
|
+
"t.co/i/adsct",
|
|
31
|
+
"pixel.advertising.com",
|
|
32
|
+
"hotjar.com",
|
|
33
|
+
"clarity.ms",
|
|
34
|
+
"quantserve.com",
|
|
35
|
+
"scorecardresearch.com",
|
|
36
|
+
"newrelic.com",
|
|
37
|
+
"nr-data.net",
|
|
38
|
+
"segment.io",
|
|
39
|
+
"segment.com",
|
|
40
|
+
"amplitude.com",
|
|
41
|
+
"mixpanel.com",
|
|
42
|
+
"heap.io",
|
|
43
|
+
"fullstory.com",
|
|
44
|
+
"moatads.com",
|
|
45
|
+
"criteo.com",
|
|
46
|
+
"adsrvr.org",
|
|
47
|
+
"rubiconproject.com",
|
|
48
|
+
"pubmatic.com",
|
|
49
|
+
"openx.net",
|
|
50
|
+
"appnexus.com",
|
|
51
|
+
"amazon-adsystem.com",
|
|
52
|
+
"media.net",
|
|
53
|
+
"yieldmo.com",
|
|
54
|
+
"triplelift.com",
|
|
55
|
+
"sharethrough.com",
|
|
56
|
+
"prebid.",
|
|
57
|
+
"smaato.net",
|
|
58
|
+
"indexworm.com",
|
|
59
|
+
"casalemedia.com",
|
|
60
|
+
"outbrain.com",
|
|
61
|
+
"taboola.com",
|
|
62
|
+
"revcontent.com",
|
|
63
|
+
"mgid.com",
|
|
64
|
+
"tawk.to",
|
|
65
|
+
"intercom.io",
|
|
66
|
+
"drift.com",
|
|
67
|
+
"hs-scripts.com",
|
|
68
|
+
"zopim.com",
|
|
69
|
+
"livechatinc.com",
|
|
70
|
+
"userlike.com",
|
|
71
|
+
"onetrust.com",
|
|
72
|
+
"cookielaw.org",
|
|
73
|
+
"cookieinformation.com",
|
|
74
|
+
"trustarc.com",
|
|
75
|
+
"/ads/",
|
|
76
|
+
"/ad/",
|
|
77
|
+
"/banner/",
|
|
78
|
+
"/banners/",
|
|
79
|
+
"/pixel/",
|
|
80
|
+
"/beacon/",
|
|
81
|
+
"/tracking/",
|
|
82
|
+
"/tracker/",
|
|
83
|
+
"/remarketing/",
|
|
84
|
+
"/conversion/",
|
|
85
|
+
"1x1.gif",
|
|
86
|
+
"spacer.gif",
|
|
87
|
+
"blank.gif",
|
|
88
|
+
"transparent.gif"
|
|
89
|
+
];
|
|
90
|
+
var IMAGE_EXTS = /* @__PURE__ */ new Set([".jpg", ".jpeg", ".png", ".webp", ".gif", ".avif", ".svg", ".tiff"]);
|
|
91
|
+
var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".webm", ".mov", ".avi", ".m4v", ".ogv", ".mkv"]);
|
|
92
|
+
var AUDIO_EXTS = /* @__PURE__ */ new Set([".mp3", ".wav", ".ogg", ".aac", ".m4a", ".flac", ".opus"]);
|
|
93
|
+
function isAdUrl(url) {
|
|
94
|
+
const lower = url.toLowerCase();
|
|
95
|
+
return AD_PATTERNS.some((p) => lower.includes(p));
|
|
96
|
+
}
|
|
97
|
+
function isDataUri(url) {
|
|
98
|
+
return url.startsWith("data:");
|
|
99
|
+
}
|
|
100
|
+
function typeFromUrl(url) {
|
|
101
|
+
try {
|
|
102
|
+
const ext = extname(new URL(url).pathname).toLowerCase();
|
|
103
|
+
if (IMAGE_EXTS.has(ext)) return "image";
|
|
104
|
+
if (VIDEO_EXTS.has(ext)) return "video";
|
|
105
|
+
if (AUDIO_EXTS.has(ext)) return "audio";
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
function typeFromMime(mime) {
|
|
111
|
+
const lower = mime.toLowerCase();
|
|
112
|
+
if (lower.startsWith("image/")) return "image";
|
|
113
|
+
if (lower.startsWith("video/")) return "video";
|
|
114
|
+
if (lower.startsWith("audio/")) return "audio";
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
function resolveUrl(raw, base) {
|
|
118
|
+
if (!raw || isDataUri(raw)) return null;
|
|
119
|
+
try {
|
|
120
|
+
return new URL(raw, base).href;
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function safeFilename(url, index) {
|
|
126
|
+
try {
|
|
127
|
+
const u = new URL(url);
|
|
128
|
+
const base = basename(u.pathname).replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
|
|
129
|
+
return base || `asset-${index}`;
|
|
130
|
+
} catch {
|
|
131
|
+
return `asset-${index}`;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function boundedText(value, max) {
|
|
135
|
+
const normalized = value?.replace(/&(?:amp|#0?38|#x26);/gi, "&").replace(/\s+/g, " ").trim();
|
|
136
|
+
return normalized ? normalized.slice(0, max) : null;
|
|
137
|
+
}
|
|
138
|
+
function dimensionsFromUrl(rawUrl) {
|
|
139
|
+
const match = rawUrl.match(/[-_/](\d{2,5})x(\d{2,5})(?=[._/?#-]|$)/i);
|
|
140
|
+
return match ? { width: Number(match[1]), height: Number(match[2]) } : null;
|
|
141
|
+
}
|
|
142
|
+
function mediaFamilyKey(rawUrl) {
|
|
143
|
+
try {
|
|
144
|
+
const url = new URL(rawUrl);
|
|
145
|
+
url.hash = "";
|
|
146
|
+
url.pathname = url.pathname.replace(/-(?:\d{2,5}x\d{2,5}|scaled|e\d{6,})(?=\.[a-z0-9]{2,6}$)/i, "");
|
|
147
|
+
for (const key of ["w", "width", "h", "height", "resize"]) url.searchParams.delete(key);
|
|
148
|
+
url.searchParams.sort();
|
|
149
|
+
return url.href;
|
|
150
|
+
} catch {
|
|
151
|
+
return rawUrl;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function candidateScore(candidate) {
|
|
155
|
+
const area = (candidate.width ?? 0) * (candidate.height ?? 0);
|
|
156
|
+
const methodBoost = candidate.discoveryMethods.some((method) => method.includes("lightbox")) ? 1e12 : 0;
|
|
157
|
+
const originalBoost = /-(?:\d{2,5}x\d{2,5}|scaled|e\d{6,})(?=\.[a-z0-9]{2,6}(?:[?#]|$))/i.test(candidate.url) ? 0 : 1e11;
|
|
158
|
+
return methodBoost + originalBoost + area;
|
|
159
|
+
}
|
|
160
|
+
function mergeCandidate(target, incoming) {
|
|
161
|
+
const existing = target.get(incoming.url);
|
|
162
|
+
if (!existing) {
|
|
163
|
+
target.set(incoming.url, incoming);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
existing.discoveryMethods = [.../* @__PURE__ */ new Set([...existing.discoveryMethods, ...incoming.discoveryMethods])];
|
|
167
|
+
existing.altTexts = [.../* @__PURE__ */ new Set([...existing.altTexts, ...incoming.altTexts])];
|
|
168
|
+
existing.contexts = [.../* @__PURE__ */ new Set([...existing.contexts, ...incoming.contexts])];
|
|
169
|
+
if (!existing.type && incoming.type) existing.type = incoming.type;
|
|
170
|
+
if ((incoming.width ?? 0) * (incoming.height ?? 0) > (existing.width ?? 0) * (existing.height ?? 0)) {
|
|
171
|
+
existing.width = incoming.width;
|
|
172
|
+
existing.height = incoming.height;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function discoverStaticMedia(html, baseUrl) {
|
|
176
|
+
const found = /* @__PURE__ */ new Map();
|
|
177
|
+
const add = (raw, method, explicitType = null, alt, context, dimensions) => {
|
|
178
|
+
const resolved = raw ? resolveUrl(raw.trim().replace(/&/g, "&"), baseUrl) : null;
|
|
179
|
+
if (!resolved || isAdUrl(resolved)) return;
|
|
180
|
+
const inferredDimensions = dimensions ?? dimensionsFromUrl(resolved);
|
|
181
|
+
mergeCandidate(found, {
|
|
182
|
+
url: resolved,
|
|
183
|
+
type: explicitType ?? typeFromUrl(resolved),
|
|
184
|
+
discoveryMethods: [method],
|
|
185
|
+
altTexts: alt ? [alt] : [],
|
|
186
|
+
contexts: context ? [context] : [],
|
|
187
|
+
width: inferredDimensions?.width ?? null,
|
|
188
|
+
height: inferredDimensions?.height ?? null
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
const scanSrcset = (value, method, type, alt) => {
|
|
192
|
+
for (const part of (value ?? "").split(",")) {
|
|
193
|
+
const [url, descriptor] = part.trim().split(/\s+/);
|
|
194
|
+
if (!url) continue;
|
|
195
|
+
const width = descriptor?.match(/^(\d+)w$/)?.[1];
|
|
196
|
+
add(url, method, type, alt, null, width ? { width: Number(width), height: 0 } : null);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
const scan = (source, suffix = "") => {
|
|
200
|
+
for (const match of source.matchAll(/<img\b([^>]*)>/gi)) {
|
|
201
|
+
const attrs = match[1];
|
|
202
|
+
const alt = boundedText(attrs.match(/\balt\s*=\s*["']([^"']*)["']/i)?.[1], 500);
|
|
203
|
+
const width = Number(attrs.match(/\bwidth\s*=\s*["']?(\d+)/i)?.[1] ?? 0);
|
|
204
|
+
const height = Number(attrs.match(/\bheight\s*=\s*["']?(\d+)/i)?.[1] ?? 0);
|
|
205
|
+
const dimensions = width > 0 && height > 0 ? { width, height } : null;
|
|
206
|
+
for (const attr of ["src", "data-src", "data-lazy-src", "data-original", "data-bg", "data-background", "data-bg-url", "data-lazy-bg", "data-echo"]) {
|
|
207
|
+
const value = attrs.match(new RegExp(`\\b${attr}\\s*=\\s*["']([^"']+)["']`, "i"))?.[1];
|
|
208
|
+
add(value, `static-${attr}${suffix}`, "image", alt, null, dimensions);
|
|
209
|
+
}
|
|
210
|
+
scanSrcset(attrs.match(/\bsrcset\s*=\s*["']([^"']+)["']/i)?.[1], `static-srcset${suffix}`, "image", alt);
|
|
211
|
+
scanSrcset(attrs.match(/\bdata-srcset\s*=\s*["']([^"']+)["']/i)?.[1], `static-data-srcset${suffix}`, "image", alt);
|
|
212
|
+
}
|
|
213
|
+
for (const match of source.matchAll(/<source\b([^>]*)>/gi)) {
|
|
214
|
+
const attrs = match[1];
|
|
215
|
+
const declared = attrs.match(/\btype\s*=\s*["']([^"']+)["']/i)?.[1] ?? "";
|
|
216
|
+
const type = declared.startsWith("video/") ? "video" : declared.startsWith("audio/") ? "audio" : "image";
|
|
217
|
+
add(attrs.match(/\bsrc\s*=\s*["']([^"']+)["']/i)?.[1], `static-source${suffix}`, type);
|
|
218
|
+
scanSrcset(attrs.match(/\bsrcset\s*=\s*["']([^"']+)["']/i)?.[1], `static-source-srcset${suffix}`, type);
|
|
219
|
+
}
|
|
220
|
+
for (const match of source.matchAll(/<(video|audio)\b([^>]*)>/gi)) {
|
|
221
|
+
const type = match[1].toLowerCase();
|
|
222
|
+
const attrs = match[2];
|
|
223
|
+
add(attrs.match(/\bsrc\s*=\s*["']([^"']+)["']/i)?.[1], `static-${type}${suffix}`, type);
|
|
224
|
+
if (type === "video") add(attrs.match(/\bposter\s*=\s*["']([^"']+)["']/i)?.[1], `static-video-poster${suffix}`, "image");
|
|
225
|
+
}
|
|
226
|
+
for (const match of source.matchAll(/<meta\b([^>]*)>/gi)) {
|
|
227
|
+
const attrs = match[1];
|
|
228
|
+
const prop = attrs.match(/\b(?:property|name)\s*=\s*["']([^"']+)["']/i)?.[1]?.toLowerCase();
|
|
229
|
+
if (prop === "og:image" || prop === "og:image:url" || prop === "twitter:image" || prop === "twitter:image:src") {
|
|
230
|
+
add(attrs.match(/\bcontent\s*=\s*["']([^"']+)["']/i)?.[1], `static-${prop}${suffix}`, "image");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
for (const match of source.matchAll(/<link\b([^>]*)>/gi)) {
|
|
234
|
+
const attrs = match[1];
|
|
235
|
+
if (/\brel\s*=\s*["'][^"']*(?:icon|apple-touch-icon)[^"']*["']/i.test(attrs)) {
|
|
236
|
+
add(attrs.match(/\bhref\s*=\s*["']([^"']+)["']/i)?.[1], `static-icon${suffix}`, "image");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
for (const match of source.matchAll(/<image\b([^>]*)>/gi)) {
|
|
240
|
+
const attrs = match[1];
|
|
241
|
+
add(attrs.match(/\b(?:href|xlink:href)\s*=\s*["']([^"']+)["']/i)?.[1], `static-svg-image${suffix}`, "image");
|
|
242
|
+
}
|
|
243
|
+
for (const match of source.matchAll(/<a\b([^>]*)>[\s\S]*?<img\b([^>]*)>[\s\S]*?<\/a>/gi)) {
|
|
244
|
+
const anchorAttrs = match[1];
|
|
245
|
+
const href = anchorAttrs.match(/\bhref\s*=\s*["']([^"']+)["']/i)?.[1];
|
|
246
|
+
if (!href) continue;
|
|
247
|
+
if (/lightbox|gallery|fancybox|glightbox|swipebox|elementor-open-lightbox/i.test(anchorAttrs) || /\.(?:avif|gif|jpe?g|png|svg|webp)(?:[?#]|$)/i.test(href)) {
|
|
248
|
+
const alt = boundedText(match[2].match(/\balt\s*=\s*["']([^"']*)["']/i)?.[1], 500);
|
|
249
|
+
add(href, `static-lightbox-target${suffix}`, "image", alt);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
for (const match of source.matchAll(/(?:background(?:-image)?\s*:\s*)?url\(\s*(?:["']|"|�?34;|'|�?39;)?([^"')&]+)(?:["']|"|�?34;|'|�?39;)?\s*\)/gi)) {
|
|
253
|
+
add(match[1], `static-css-url${suffix}`, "image");
|
|
254
|
+
}
|
|
255
|
+
for (const match of source.matchAll(/["'](https?:\/\/[^"'\s<>]+\.(?:avif|gif|jpe?g|png|svg|webp|mp4|webm|mp3|ogg)(?:\?[^"'\s<>]*)?)["']/gi)) {
|
|
256
|
+
add(match[1], `static-bare-url${suffix}`);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
scan(html);
|
|
260
|
+
const unescaped = html.replace(/\\u003[cC]/g, "<").replace(/\\u003[eE]/g, ">").replace(/\\u0026/gi, "&").replace(/\\["']/g, (match) => match.slice(1)).replace(/\\\//g, "/");
|
|
261
|
+
if (unescaped !== html) scan(unescaped, "-json-unescaped");
|
|
262
|
+
return [...found.values()];
|
|
263
|
+
}
|
|
264
|
+
function mergeMediaDiscovery(staticAssets, rendered, maxAssets = 100) {
|
|
265
|
+
const exact = /* @__PURE__ */ new Map();
|
|
266
|
+
for (const asset of staticAssets) mergeCandidate(exact, asset);
|
|
267
|
+
for (const asset of rendered?.assets ?? []) mergeCandidate(exact, { ...asset, type: asset.type });
|
|
268
|
+
const families = /* @__PURE__ */ new Map();
|
|
269
|
+
for (const asset of exact.values()) {
|
|
270
|
+
const key = mediaFamilyKey(asset.url);
|
|
271
|
+
const family = families.get(key) ?? [];
|
|
272
|
+
family.push(asset);
|
|
273
|
+
families.set(key, family);
|
|
274
|
+
}
|
|
275
|
+
return [...families.values()].map((family) => {
|
|
276
|
+
const ordered = [...family].sort((a, b) => candidateScore(b) - candidateScore(a) || a.url.localeCompare(b.url));
|
|
277
|
+
const preferred = { ...ordered[0] };
|
|
278
|
+
preferred.discoveryMethods = [...new Set(family.flatMap((asset) => asset.discoveryMethods))];
|
|
279
|
+
preferred.altTexts = [...new Set(family.flatMap((asset) => asset.altTexts))];
|
|
280
|
+
preferred.contexts = [...new Set(family.flatMap((asset) => asset.contexts))];
|
|
281
|
+
return { ...preferred, variants: ordered.slice(1).map((asset) => asset.url) };
|
|
282
|
+
}).sort((a, b) => candidateScore(b) - candidateScore(a) || a.url.localeCompare(b.url)).slice(0, Math.max(1, maxAssets));
|
|
283
|
+
}
|
|
284
|
+
async function downloadAsset(url, destDir, filename, options = {}) {
|
|
285
|
+
const maxBytes = Math.max(1, Math.min(options.maxBytes ?? 50 * 1024 * 1024, 100 * 1024 * 1024));
|
|
286
|
+
let target = url;
|
|
287
|
+
let res = null;
|
|
288
|
+
for (let redirects = 0; redirects <= 5; redirects++) {
|
|
289
|
+
const checked = await validatePublicHttpUrl(target, { field: "media URL" });
|
|
290
|
+
if (checked.error || !checked.parsed) throw new Error(checked.error ?? "Media URL was rejected");
|
|
291
|
+
res = await fetch(checked.parsed.href, {
|
|
292
|
+
signal: AbortSignal.timeout(15e3),
|
|
293
|
+
redirect: "manual"
|
|
294
|
+
});
|
|
295
|
+
if (res.status >= 300 && res.status < 400) {
|
|
296
|
+
const location = res.headers.get("location");
|
|
297
|
+
if (!location) throw new Error(`HTTP ${res.status} redirect did not include Location`);
|
|
298
|
+
target = new URL(location, checked.parsed.href).href;
|
|
299
|
+
res = null;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
if (!res) throw new Error("Media download exceeded five redirects");
|
|
305
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
306
|
+
if (!res.body) throw new Error("Empty response body");
|
|
307
|
+
const mimeType = res.headers.get("content-type")?.split(";")[0].trim() ?? null;
|
|
308
|
+
if (options.expectedType && (!mimeType || typeFromMime(mimeType) !== options.expectedType)) {
|
|
309
|
+
throw new Error(`Expected ${options.expectedType} content but received ${mimeType ?? "no content-type"}`);
|
|
310
|
+
}
|
|
311
|
+
const declaredBytes = Number(res.headers.get("content-length") ?? 0);
|
|
312
|
+
if (Number.isFinite(declaredBytes) && declaredBytes > maxBytes) {
|
|
313
|
+
throw new Error(`Media asset exceeds ${maxBytes} byte limit`);
|
|
314
|
+
}
|
|
315
|
+
let dest = join(destDir, filename);
|
|
316
|
+
if (mimeType && !extname(filename)) {
|
|
317
|
+
const mimeExt = {
|
|
318
|
+
"image/jpeg": ".jpg",
|
|
319
|
+
"image/png": ".png",
|
|
320
|
+
"image/webp": ".webp",
|
|
321
|
+
"image/gif": ".gif",
|
|
322
|
+
"image/svg+xml": ".svg",
|
|
323
|
+
"image/avif": ".avif",
|
|
324
|
+
"video/mp4": ".mp4",
|
|
325
|
+
"video/webm": ".webm",
|
|
326
|
+
"audio/mpeg": ".mp3",
|
|
327
|
+
"audio/ogg": ".ogg",
|
|
328
|
+
"audio/wav": ".wav"
|
|
329
|
+
};
|
|
330
|
+
const ext = mimeExt[mimeType];
|
|
331
|
+
if (ext) dest = dest + ext;
|
|
332
|
+
}
|
|
333
|
+
const writer = createWriteStream(dest);
|
|
334
|
+
await new Promise((resolve, reject) => {
|
|
335
|
+
writer.once("open", () => resolve());
|
|
336
|
+
writer.once("error", reject);
|
|
337
|
+
});
|
|
338
|
+
let streamedBytes = 0;
|
|
339
|
+
const limiter = new Transform({
|
|
340
|
+
transform(chunk, _encoding, callback) {
|
|
341
|
+
const bytes = chunk.length;
|
|
342
|
+
if (streamedBytes + bytes > maxBytes) {
|
|
343
|
+
callback(new Error(`Media asset exceeds ${maxBytes} byte limit`));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (options.consumeBytes && !options.consumeBytes(bytes)) {
|
|
347
|
+
callback(new Error("Media export total byte limit exceeded"));
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
streamedBytes += bytes;
|
|
351
|
+
callback(null, chunk);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
try {
|
|
355
|
+
await pipeline(Readable.fromWeb(res.body), limiter, writer);
|
|
356
|
+
} catch (error) {
|
|
357
|
+
writer.destroy();
|
|
358
|
+
rmSync(dest, { force: true });
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
const { statSync } = await import("fs");
|
|
362
|
+
const sizeBytes = statSync(dest).size;
|
|
363
|
+
return { savedPath: dest, sizeBytes, mimeType };
|
|
364
|
+
}
|
|
365
|
+
async function harvestPageMedia(html, pageUrl, options = {}) {
|
|
366
|
+
const types = options.types ?? ["image", "video", "audio"];
|
|
367
|
+
const typesSet = new Set(types);
|
|
368
|
+
const staticAssets = discoverStaticMedia(html, pageUrl);
|
|
369
|
+
const maxAssets = Math.max(1, Math.min(options.maxAssets ?? 100, 250));
|
|
370
|
+
const allDiscovered = mergeMediaDiscovery(staticAssets, options.rendered, 2e3);
|
|
371
|
+
const discovered = allDiscovered.slice(0, maxAssets);
|
|
372
|
+
const totalFound = (/* @__PURE__ */ new Set([
|
|
373
|
+
...staticAssets.map((asset) => asset.url),
|
|
374
|
+
...(options.rendered?.assets ?? []).map((asset) => asset.url)
|
|
375
|
+
])).size;
|
|
376
|
+
const kept = discovered.flatMap((asset) => {
|
|
377
|
+
const type = asset.type ?? typeFromUrl(asset.url);
|
|
378
|
+
return type && typesSet.has(type) ? [{ ...asset, type }] : [];
|
|
379
|
+
});
|
|
380
|
+
const filteredCount = totalFound - kept.length;
|
|
381
|
+
const retentionLimitReached = allDiscovered.length > maxAssets;
|
|
382
|
+
const exhausted = options.rendered?.exhausted === true && !retentionLimitReached;
|
|
383
|
+
const stopReason = retentionLimitReached ? "asset_limit" : options.rendered?.stopReason ?? "render_unavailable";
|
|
384
|
+
const warnings = retentionLimitReached ? [`Media discovery found ${allDiscovered.length} responsive families; only the requested ${maxAssets} records were retained.`] : options.rendered ? options.rendered.exhausted ? [] : [`Rendered media discovery stopped with ${options.rendered.stopReason}; the inventory may be incomplete.`] : ["Rendered media discovery was unavailable; this manifest contains static-source evidence only."];
|
|
385
|
+
const baseManifest = {
|
|
386
|
+
pageUrl,
|
|
387
|
+
staticFound: staticAssets.length,
|
|
388
|
+
renderedFound: options.rendered?.assets.length ?? 0,
|
|
389
|
+
filteredCount,
|
|
390
|
+
totalFound,
|
|
391
|
+
completeness: exhausted ? "complete" : "partial",
|
|
392
|
+
exhausted,
|
|
393
|
+
stopReason,
|
|
394
|
+
scrollRounds: options.rendered?.scrollRounds ?? 0,
|
|
395
|
+
warnings,
|
|
396
|
+
artifact: null
|
|
397
|
+
};
|
|
398
|
+
if (options.outputDir === null) {
|
|
399
|
+
return {
|
|
400
|
+
...baseManifest,
|
|
401
|
+
outputDir: null,
|
|
402
|
+
assets: kept.map((asset, i) => ({
|
|
403
|
+
...asset,
|
|
404
|
+
mimeType: null,
|
|
405
|
+
filename: safeFilename(asset.url, i),
|
|
406
|
+
savedPath: null,
|
|
407
|
+
sizeBytes: null,
|
|
408
|
+
sha256: null,
|
|
409
|
+
downloadStatus: "not_attempted",
|
|
410
|
+
downloadError: null,
|
|
411
|
+
inlinePreview: null
|
|
412
|
+
}))
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
const domain = (() => {
|
|
416
|
+
try {
|
|
417
|
+
return new URL(pageUrl).hostname.replace(/^www\./, "");
|
|
418
|
+
} catch {
|
|
419
|
+
return "unknown";
|
|
420
|
+
}
|
|
421
|
+
})();
|
|
422
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
423
|
+
const outDir = options.outputDir ?? join(homedir(), "Downloads", "mcp-scraper", "media", `${stamp}-${domain}`);
|
|
424
|
+
mkdirSync(outDir, { recursive: true });
|
|
425
|
+
const assets = [];
|
|
426
|
+
await Promise.allSettled(
|
|
427
|
+
kept.map(async (asset, i) => {
|
|
428
|
+
const filename = safeFilename(asset.url, i);
|
|
429
|
+
try {
|
|
430
|
+
const { savedPath, sizeBytes, mimeType } = await downloadAsset(asset.url, outDir, filename);
|
|
431
|
+
const resolvedType = mimeType ? typeFromMime(mimeType) ?? asset.type : asset.type;
|
|
432
|
+
assets.push({
|
|
433
|
+
...asset,
|
|
434
|
+
type: resolvedType,
|
|
435
|
+
mimeType,
|
|
436
|
+
filename: basename(savedPath),
|
|
437
|
+
savedPath,
|
|
438
|
+
sizeBytes,
|
|
439
|
+
sha256: null,
|
|
440
|
+
downloadStatus: "downloaded",
|
|
441
|
+
downloadError: null,
|
|
442
|
+
inlinePreview: null
|
|
443
|
+
});
|
|
444
|
+
} catch (error) {
|
|
445
|
+
assets.push({
|
|
446
|
+
...asset,
|
|
447
|
+
mimeType: null,
|
|
448
|
+
filename,
|
|
449
|
+
savedPath: null,
|
|
450
|
+
sizeBytes: null,
|
|
451
|
+
sha256: null,
|
|
452
|
+
downloadStatus: "failed",
|
|
453
|
+
downloadError: error instanceof Error ? error.message : "media_download_failed",
|
|
454
|
+
inlinePreview: null
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
})
|
|
458
|
+
);
|
|
459
|
+
assets.sort((a, b) => {
|
|
460
|
+
if (a.savedPath && !b.savedPath) return -1;
|
|
461
|
+
if (!a.savedPath && b.savedPath) return 1;
|
|
462
|
+
return a.url.localeCompare(b.url);
|
|
463
|
+
});
|
|
464
|
+
return { ...baseManifest, outputDir: outDir, assets };
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/api/site-extract-artifacts.ts
|
|
468
|
+
var SITE_EXTRACT_ARTIFACT_PREFIX = "site-extracts/";
|
|
469
|
+
var SITE_EXTRACT_ARTIFACT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
470
|
+
var SITE_EXTRACT_DOWNLOAD_TTL_MS = 15 * 60 * 1e3;
|
|
471
|
+
function siteExtractArtifactToken() {
|
|
472
|
+
return process.env.SITE_EXTRACT_ARTIFACT_READ_WRITE_TOKEN?.trim() || process.env.PRIVATE_ARTIFACT_READ_WRITE_TOKEN?.trim() || process.env.CONNECTED_DATA_READ_WRITE_TOKEN?.trim() || process.env.CONNECTED_DATA_BLOB_READ_WRITE_TOKEN?.trim() || null;
|
|
473
|
+
}
|
|
474
|
+
function hostedByEnvironment() {
|
|
475
|
+
return process.env.VERCEL === "1" || process.env.NODE_ENV === "production";
|
|
476
|
+
}
|
|
477
|
+
function policy() {
|
|
478
|
+
return {
|
|
479
|
+
prefix: SITE_EXTRACT_ARTIFACT_PREFIX,
|
|
480
|
+
artifactTtlMs: SITE_EXTRACT_ARTIFACT_TTL_MS,
|
|
481
|
+
downloadTtlMs: SITE_EXTRACT_DOWNLOAD_TTL_MS,
|
|
482
|
+
token: siteExtractArtifactToken()
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
function siteExtractArtifactOwnerId(artifactId) {
|
|
486
|
+
return privateArtifactOwnerId(artifactId, SITE_EXTRACT_ARTIFACT_PREFIX);
|
|
487
|
+
}
|
|
488
|
+
async function createSiteExtractBundleArtifactStream(args) {
|
|
489
|
+
const pointer = await createPrivateArtifactFromStream({
|
|
490
|
+
policy: policy(),
|
|
491
|
+
ownerId: args.ownerId,
|
|
492
|
+
artifactKey: `${args.jobId}.zip`,
|
|
493
|
+
createdAt: args.createdAt,
|
|
494
|
+
filename: `${args.jobId}-site-export.zip`,
|
|
495
|
+
contentType: "application/zip",
|
|
496
|
+
content: args.content
|
|
497
|
+
});
|
|
498
|
+
return {
|
|
499
|
+
key: pointer.artifactId,
|
|
500
|
+
url: pointer.downloadUrl ?? "",
|
|
501
|
+
bytes: pointer.bytes,
|
|
502
|
+
contentType: pointer.contentType,
|
|
503
|
+
filename: pointer.filename,
|
|
504
|
+
sha256: pointer.sha256,
|
|
505
|
+
expiresAt: pointer.expiresAt,
|
|
506
|
+
downloadUrlExpiresAt: pointer.downloadUrlExpiresAt
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
async function renewSiteExtractArtifactDownload(args) {
|
|
510
|
+
return renewPrivateArtifactDownload({
|
|
511
|
+
policy: policy(),
|
|
512
|
+
artifactId: args.artifactId,
|
|
513
|
+
ownerId: args.ownerId
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
async function readSiteExtractArtifactBuffer(artifactId) {
|
|
517
|
+
return readPrivateArtifactBuffer({
|
|
518
|
+
policy: policy(),
|
|
519
|
+
artifactId,
|
|
520
|
+
maxBytes: 50 * 1024 * 1024
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
async function readOwnedSiteExtractArtifactBuffer(args) {
|
|
524
|
+
if (siteExtractArtifactOwnerId(args.artifactId) !== args.ownerId) return null;
|
|
525
|
+
return readSiteExtractArtifactBuffer(args.artifactId);
|
|
526
|
+
}
|
|
527
|
+
async function cleanupExpiredSiteExtractArtifacts(args = {}) {
|
|
528
|
+
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
529
|
+
const token = siteExtractArtifactToken();
|
|
530
|
+
if (!token) return { deleted: 0, store: hostedByEnvironment() ? "none" : "local" };
|
|
531
|
+
if (!args.force && !(now.getUTCHours() === 3 && now.getUTCMinutes() === 21)) {
|
|
532
|
+
return { deleted: 0, store: "private-vercel-blob", skipped: true };
|
|
533
|
+
}
|
|
534
|
+
const cutoff = now.getTime() - SITE_EXTRACT_ARTIFACT_TTL_MS;
|
|
535
|
+
const { list, del } = await import("@vercel/blob");
|
|
536
|
+
let cursor;
|
|
537
|
+
let deleted = 0;
|
|
538
|
+
for (let page = 0; page < 20; page += 1) {
|
|
539
|
+
const result = await list({ prefix: SITE_EXTRACT_ARTIFACT_PREFIX, token, limit: 1e3, cursor });
|
|
540
|
+
const expired = result.blobs.filter((blob) => new Date(blob.uploadedAt).getTime() <= cutoff);
|
|
541
|
+
if (expired.length > 0) {
|
|
542
|
+
await del(expired.map((blob) => blob.pathname), { token });
|
|
543
|
+
deleted += expired.length;
|
|
544
|
+
}
|
|
545
|
+
if (!result.hasMore || !result.cursor) break;
|
|
546
|
+
cursor = result.cursor;
|
|
547
|
+
}
|
|
548
|
+
return { deleted, store: "private-vercel-blob" };
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export {
|
|
552
|
+
typeFromMime,
|
|
553
|
+
discoverStaticMedia,
|
|
554
|
+
downloadAsset,
|
|
555
|
+
harvestPageMedia,
|
|
556
|
+
SITE_EXTRACT_ARTIFACT_PREFIX,
|
|
557
|
+
createSiteExtractBundleArtifactStream,
|
|
558
|
+
renewSiteExtractArtifactDownload,
|
|
559
|
+
readOwnedSiteExtractArtifactBuffer,
|
|
560
|
+
cleanupExpiredSiteExtractArtifacts
|
|
561
|
+
};
|
|
562
|
+
//# sourceMappingURL=chunk-SG2PEHR3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/media-extractor.ts","../src/api/site-extract-artifacts.ts"],"sourcesContent":["import { createWriteStream, mkdirSync, rmSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join, extname, basename } from 'node:path'\nimport { pipeline } from 'node:stream/promises'\nimport { Readable, Transform } from 'node:stream'\nimport { validatePublicHttpUrl } from '../api/url-utils.js'\nimport type { RenderedMediaDiscovery } from './rendered-media.js'\n\nconst AD_PATTERNS: string[] = [\n 'doubleclick.net', 'googlesyndication.com', 'googletagmanager.com',\n 'google-analytics.com', 'googletagservices.com', 'adservice.google',\n 'googletag.', 'pagead2.googlesyndication',\n 'facebook.net/tr', 'connect.facebook.net', 'fbcdn.net/rsrc',\n 'analytics.twitter.com', 'static.ads-twitter.com', 'ads.twitter.com',\n 't.co/i/adsct', 'pixel.advertising.com',\n 'hotjar.com', 'clarity.ms', 'quantserve.com', 'scorecardresearch.com',\n 'newrelic.com', 'nr-data.net', 'segment.io', 'segment.com',\n 'amplitude.com', 'mixpanel.com', 'heap.io', 'fullstory.com',\n 'moatads.com', 'criteo.com', 'adsrvr.org', 'rubiconproject.com',\n 'pubmatic.com', 'openx.net', 'appnexus.com', 'amazon-adsystem.com',\n 'media.net', 'yieldmo.com', 'triplelift.com', 'sharethrough.com',\n 'prebid.', 'smaato.net', 'indexworm.com', 'casalemedia.com',\n 'outbrain.com', 'taboola.com', 'revcontent.com', 'mgid.com',\n 'tawk.to', 'intercom.io', 'drift.com', 'hs-scripts.com',\n 'zopim.com', 'livechatinc.com', 'userlike.com',\n 'onetrust.com', 'cookielaw.org', 'cookieinformation.com', 'trustarc.com',\n '/ads/', '/ad/', '/banner/', '/banners/', '/pixel/', '/beacon/',\n '/tracking/', '/tracker/', '/remarketing/', '/conversion/',\n '1x1.gif', 'spacer.gif', 'blank.gif', 'transparent.gif',\n]\n\nconst IMAGE_EXTS = new Set(['.jpg', '.jpeg', '.png', '.webp', '.gif', '.avif', '.svg', '.tiff'])\nconst VIDEO_EXTS = new Set(['.mp4', '.webm', '.mov', '.avi', '.m4v', '.ogv', '.mkv'])\nconst AUDIO_EXTS = new Set(['.mp3', '.wav', '.ogg', '.aac', '.m4a', '.flac', '.opus'])\n\nexport type MediaType = 'image' | 'video' | 'audio'\n\nexport interface MediaAsset {\n url: string\n type: MediaType\n mimeType: string | null\n filename: string\n savedPath: string | null\n sizeBytes: number | null\n discoveryMethods: string[]\n altTexts: string[]\n contexts: string[]\n width: number | null\n height: number | null\n variants: string[]\n finalUrl?: string | null\n duplicateOf?: string | null\n sha256?: string | null\n downloadStatus?: 'downloaded' | 'failed' | 'not_attempted'\n downloadError?: string | null\n inlinePreview?: { data: string; mimeType: string } | null\n}\n\nexport interface MediaManifest {\n pageUrl: string\n outputDir: string | null\n assets: MediaAsset[]\n filteredCount: number\n totalFound: number\n staticFound: number\n renderedFound: number\n completeness: 'complete' | 'partial'\n exhausted: boolean\n stopReason: 'page_exhausted' | 'asset_limit' | 'scroll_round_limit' | 'render_unavailable'\n scrollRounds: number\n warnings: string[]\n artifact: {\n artifactId: string\n filename: string\n contentType: string\n bytes: number\n sha256: string\n expiresAt: string\n downloadUrl: string | null\n downloadUrlExpiresAt: string | null\n localPath: string | null\n } | null\n}\n\nexport interface MediaExtractOptions {\n types?: MediaType[]\n outputDir?: string | null\n rendered?: RenderedMediaDiscovery | null\n maxAssets?: number\n}\n\nexport interface DiscoveredMediaCandidate {\n url: string\n type: MediaType | null\n discoveryMethods: string[]\n altTexts: string[]\n contexts: string[]\n width: number | null\n height: number | null\n}\n\nfunction isAdUrl(url: string): boolean {\n const lower = url.toLowerCase()\n return AD_PATTERNS.some(p => lower.includes(p))\n}\n\nfunction isDataUri(url: string): boolean {\n return url.startsWith('data:')\n}\n\nexport function typeFromUrl(url: string): MediaType | null {\n try {\n const ext = extname(new URL(url).pathname).toLowerCase()\n if (IMAGE_EXTS.has(ext)) return 'image'\n if (VIDEO_EXTS.has(ext)) return 'video'\n if (AUDIO_EXTS.has(ext)) return 'audio'\n } catch { /* non-parseable URL */ }\n return null\n}\n\nexport function typeFromMime(mime: string): MediaType | null {\n const lower = mime.toLowerCase()\n if (lower.startsWith('image/')) return 'image'\n if (lower.startsWith('video/')) return 'video'\n if (lower.startsWith('audio/')) return 'audio'\n return null\n}\n\nfunction resolveUrl(raw: string, base: string): string | null {\n if (!raw || isDataUri(raw)) return null\n try { return new URL(raw, base).href } catch { return null }\n}\n\nfunction safeFilename(url: string, index: number): string {\n try {\n const u = new URL(url)\n const base = basename(u.pathname).replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 80)\n return base || `asset-${index}`\n } catch {\n return `asset-${index}`\n }\n}\n\nfunction boundedText(value: string | undefined | null, max: number): string | null {\n const normalized = value?.replace(/&(?:amp|#0?38|#x26);/gi, '&').replace(/\\s+/g, ' ').trim()\n return normalized ? normalized.slice(0, max) : null\n}\n\nfunction dimensionsFromUrl(rawUrl: string): { width: number; height: number } | null {\n const match = rawUrl.match(/[-_/](\\d{2,5})x(\\d{2,5})(?=[._/?#-]|$)/i)\n return match ? { width: Number(match[1]), height: Number(match[2]) } : null\n}\n\nexport function mediaFamilyKey(rawUrl: string): string {\n try {\n const url = new URL(rawUrl)\n url.hash = ''\n url.pathname = url.pathname.replace(/-(?:\\d{2,5}x\\d{2,5}|scaled|e\\d{6,})(?=\\.[a-z0-9]{2,6}$)/i, '')\n for (const key of ['w', 'width', 'h', 'height', 'resize']) url.searchParams.delete(key)\n url.searchParams.sort()\n return url.href\n } catch {\n return rawUrl\n }\n}\n\nfunction candidateScore(candidate: DiscoveredMediaCandidate): number {\n const area = (candidate.width ?? 0) * (candidate.height ?? 0)\n const methodBoost = candidate.discoveryMethods.some(method => method.includes('lightbox')) ? 1_000_000_000_000 : 0\n const originalBoost = /-(?:\\d{2,5}x\\d{2,5}|scaled|e\\d{6,})(?=\\.[a-z0-9]{2,6}(?:[?#]|$))/i.test(candidate.url) ? 0 : 100_000_000_000\n return methodBoost + originalBoost + area\n}\n\nfunction mergeCandidate(target: Map<string, DiscoveredMediaCandidate>, incoming: DiscoveredMediaCandidate): void {\n const existing = target.get(incoming.url)\n if (!existing) {\n target.set(incoming.url, incoming)\n return\n }\n existing.discoveryMethods = [...new Set([...existing.discoveryMethods, ...incoming.discoveryMethods])]\n existing.altTexts = [...new Set([...existing.altTexts, ...incoming.altTexts])]\n existing.contexts = [...new Set([...existing.contexts, ...incoming.contexts])]\n if (!existing.type && incoming.type) existing.type = incoming.type\n if ((incoming.width ?? 0) * (incoming.height ?? 0) > (existing.width ?? 0) * (existing.height ?? 0)) {\n existing.width = incoming.width\n existing.height = incoming.height\n }\n}\n\nexport function discoverStaticMedia(html: string, baseUrl: string): DiscoveredMediaCandidate[] {\n const found = new Map<string, DiscoveredMediaCandidate>()\n const add = (\n raw: string | undefined,\n method: string,\n explicitType: MediaType | null = null,\n alt?: string | null,\n context?: string | null,\n dimensions?: { width: number; height: number } | null,\n ) => {\n const resolved = raw ? resolveUrl(raw.trim().replace(/&/g, '&'), baseUrl) : null\n if (!resolved || isAdUrl(resolved)) return\n const inferredDimensions = dimensions ?? dimensionsFromUrl(resolved)\n mergeCandidate(found, {\n url: resolved,\n type: explicitType ?? typeFromUrl(resolved),\n discoveryMethods: [method],\n altTexts: alt ? [alt] : [],\n contexts: context ? [context] : [],\n width: inferredDimensions?.width ?? null,\n height: inferredDimensions?.height ?? null,\n })\n }\n const scanSrcset = (value: string | undefined, method: string, type: MediaType, alt?: string | null) => {\n for (const part of (value ?? '').split(',')) {\n const [url, descriptor] = part.trim().split(/\\s+/)\n if (!url) continue\n const width = descriptor?.match(/^(\\d+)w$/)?.[1]\n add(url, method, type, alt, null, width ? { width: Number(width), height: 0 } : null)\n }\n }\n const scan = (source: string, suffix = '') => {\n for (const match of source.matchAll(/<img\\b([^>]*)>/gi)) {\n const attrs = match[1]\n const alt = boundedText(attrs.match(/\\balt\\s*=\\s*[\"']([^\"']*)[\"']/i)?.[1], 500)\n const width = Number(attrs.match(/\\bwidth\\s*=\\s*[\"']?(\\d+)/i)?.[1] ?? 0)\n const height = Number(attrs.match(/\\bheight\\s*=\\s*[\"']?(\\d+)/i)?.[1] ?? 0)\n const dimensions = width > 0 && height > 0 ? { width, height } : null\n for (const attr of ['src', 'data-src', 'data-lazy-src', 'data-original', 'data-bg', 'data-background', 'data-bg-url', 'data-lazy-bg', 'data-echo']) {\n const value = attrs.match(new RegExp(`\\\\b${attr}\\\\s*=\\\\s*[\"']([^\"']+)[\"']`, 'i'))?.[1]\n add(value, `static-${attr}${suffix}`, 'image', alt, null, dimensions)\n }\n scanSrcset(attrs.match(/\\bsrcset\\s*=\\s*[\"']([^\"']+)[\"']/i)?.[1], `static-srcset${suffix}`, 'image', alt)\n scanSrcset(attrs.match(/\\bdata-srcset\\s*=\\s*[\"']([^\"']+)[\"']/i)?.[1], `static-data-srcset${suffix}`, 'image', alt)\n }\n for (const match of source.matchAll(/<source\\b([^>]*)>/gi)) {\n const attrs = match[1]\n const declared = attrs.match(/\\btype\\s*=\\s*[\"']([^\"']+)[\"']/i)?.[1] ?? ''\n const type: MediaType = declared.startsWith('video/') ? 'video' : declared.startsWith('audio/') ? 'audio' : 'image'\n add(attrs.match(/\\bsrc\\s*=\\s*[\"']([^\"']+)[\"']/i)?.[1], `static-source${suffix}`, type)\n scanSrcset(attrs.match(/\\bsrcset\\s*=\\s*[\"']([^\"']+)[\"']/i)?.[1], `static-source-srcset${suffix}`, type)\n }\n for (const match of source.matchAll(/<(video|audio)\\b([^>]*)>/gi)) {\n const type = match[1].toLowerCase() as 'video' | 'audio'\n const attrs = match[2]\n add(attrs.match(/\\bsrc\\s*=\\s*[\"']([^\"']+)[\"']/i)?.[1], `static-${type}${suffix}`, type)\n if (type === 'video') add(attrs.match(/\\bposter\\s*=\\s*[\"']([^\"']+)[\"']/i)?.[1], `static-video-poster${suffix}`, 'image')\n }\n for (const match of source.matchAll(/<meta\\b([^>]*)>/gi)) {\n const attrs = match[1]\n const prop = attrs.match(/\\b(?:property|name)\\s*=\\s*[\"']([^\"']+)[\"']/i)?.[1]?.toLowerCase()\n if (prop === 'og:image' || prop === 'og:image:url' || prop === 'twitter:image' || prop === 'twitter:image:src') {\n add(attrs.match(/\\bcontent\\s*=\\s*[\"']([^\"']+)[\"']/i)?.[1], `static-${prop}${suffix}`, 'image')\n }\n }\n for (const match of source.matchAll(/<link\\b([^>]*)>/gi)) {\n const attrs = match[1]\n if (/\\brel\\s*=\\s*[\"'][^\"']*(?:icon|apple-touch-icon)[^\"']*[\"']/i.test(attrs)) {\n add(attrs.match(/\\bhref\\s*=\\s*[\"']([^\"']+)[\"']/i)?.[1], `static-icon${suffix}`, 'image')\n }\n }\n for (const match of source.matchAll(/<image\\b([^>]*)>/gi)) {\n const attrs = match[1]\n add(attrs.match(/\\b(?:href|xlink:href)\\s*=\\s*[\"']([^\"']+)[\"']/i)?.[1], `static-svg-image${suffix}`, 'image')\n }\n for (const match of source.matchAll(/<a\\b([^>]*)>[\\s\\S]*?<img\\b([^>]*)>[\\s\\S]*?<\\/a>/gi)) {\n const anchorAttrs = match[1]\n const href = anchorAttrs.match(/\\bhref\\s*=\\s*[\"']([^\"']+)[\"']/i)?.[1]\n if (!href) continue\n if (\n /lightbox|gallery|fancybox|glightbox|swipebox|elementor-open-lightbox/i.test(anchorAttrs)\n || /\\.(?:avif|gif|jpe?g|png|svg|webp)(?:[?#]|$)/i.test(href)\n ) {\n const alt = boundedText(match[2].match(/\\balt\\s*=\\s*[\"']([^\"']*)[\"']/i)?.[1], 500)\n add(href, `static-lightbox-target${suffix}`, 'image', alt)\n }\n }\n for (const match of source.matchAll(/(?:background(?:-image)?\\s*:\\s*)?url\\(\\s*(?:[\"']|"|�?34;|'|�?39;)?([^\"')&]+)(?:[\"']|"|�?34;|'|�?39;)?\\s*\\)/gi)) {\n add(match[1], `static-css-url${suffix}`, 'image')\n }\n for (const match of source.matchAll(/[\"'](https?:\\/\\/[^\"'\\s<>]+\\.(?:avif|gif|jpe?g|png|svg|webp|mp4|webm|mp3|ogg)(?:\\?[^\"'\\s<>]*)?)[\"']/gi)) {\n add(match[1], `static-bare-url${suffix}`)\n }\n }\n\n scan(html)\n const unescaped = html\n .replace(/\\\\u003[cC]/g, '<').replace(/\\\\u003[eE]/g, '>').replace(/\\\\u0026/gi, '&')\n .replace(/\\\\[\"']/g, match => match.slice(1)).replace(/\\\\\\//g, '/')\n if (unescaped !== html) scan(unescaped, '-json-unescaped')\n return [...found.values()]\n}\n\nexport function mergeMediaDiscovery(\n staticAssets: DiscoveredMediaCandidate[],\n rendered: RenderedMediaDiscovery | null | undefined,\n maxAssets = 100,\n): Array<DiscoveredMediaCandidate & { variants: string[] }> {\n const exact = new Map<string, DiscoveredMediaCandidate>()\n for (const asset of staticAssets) mergeCandidate(exact, asset)\n for (const asset of rendered?.assets ?? []) mergeCandidate(exact, { ...asset, type: asset.type })\n\n const families = new Map<string, DiscoveredMediaCandidate[]>()\n for (const asset of exact.values()) {\n const key = mediaFamilyKey(asset.url)\n const family = families.get(key) ?? []\n family.push(asset)\n families.set(key, family)\n }\n return [...families.values()]\n .map(family => {\n const ordered = [...family].sort((a, b) => candidateScore(b) - candidateScore(a) || a.url.localeCompare(b.url))\n const preferred = { ...ordered[0] }\n preferred.discoveryMethods = [...new Set(family.flatMap(asset => asset.discoveryMethods))]\n preferred.altTexts = [...new Set(family.flatMap(asset => asset.altTexts))]\n preferred.contexts = [...new Set(family.flatMap(asset => asset.contexts))]\n return { ...preferred, variants: ordered.slice(1).map(asset => asset.url) }\n })\n .sort((a, b) => candidateScore(b) - candidateScore(a) || a.url.localeCompare(b.url))\n .slice(0, Math.max(1, maxAssets))\n}\n\nexport function extractMediaUrls(html: string, baseUrl: string): string[] {\n return mergeMediaDiscovery(discoverStaticMedia(html, baseUrl), null, 2_000).map(asset => asset.url)\n}\n\nexport async function downloadAsset(\n url: string,\n destDir: string,\n filename: string,\n options: {\n expectedType?: MediaType\n maxBytes?: number\n consumeBytes?: (bytes: number) => boolean\n } = {},\n): Promise<{ savedPath: string; sizeBytes: number; mimeType: string | null }> {\n const maxBytes = Math.max(1, Math.min(options.maxBytes ?? 50 * 1024 * 1024, 100 * 1024 * 1024))\n let target = url\n let res: Response | null = null\n for (let redirects = 0; redirects <= 5; redirects++) {\n const checked = await validatePublicHttpUrl(target, { field: 'media URL' })\n if (checked.error || !checked.parsed) throw new Error(checked.error ?? 'Media URL was rejected')\n res = await fetch(checked.parsed.href, {\n signal: AbortSignal.timeout(15_000),\n redirect: 'manual',\n })\n if (res.status >= 300 && res.status < 400) {\n const location = res.headers.get('location')\n if (!location) throw new Error(`HTTP ${res.status} redirect did not include Location`)\n target = new URL(location, checked.parsed.href).href\n res = null\n continue\n }\n break\n }\n if (!res) throw new Error('Media download exceeded five redirects')\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n if (!res.body) throw new Error('Empty response body')\n\n const mimeType = res.headers.get('content-type')?.split(';')[0].trim() ?? null\n if (options.expectedType && (!mimeType || typeFromMime(mimeType) !== options.expectedType)) {\n throw new Error(`Expected ${options.expectedType} content but received ${mimeType ?? 'no content-type'}`)\n }\n const declaredBytes = Number(res.headers.get('content-length') ?? 0)\n if (Number.isFinite(declaredBytes) && declaredBytes > maxBytes) {\n throw new Error(`Media asset exceeds ${maxBytes} byte limit`)\n }\n\n let dest = join(destDir, filename)\n if (mimeType && !extname(filename)) {\n const mimeExt: Record<string, string> = {\n 'image/jpeg': '.jpg', 'image/png': '.png', 'image/webp': '.webp',\n 'image/gif': '.gif', 'image/svg+xml': '.svg', 'image/avif': '.avif',\n 'video/mp4': '.mp4', 'video/webm': '.webm',\n 'audio/mpeg': '.mp3', 'audio/ogg': '.ogg', 'audio/wav': '.wav',\n }\n const ext = mimeExt[mimeType]\n if (ext) dest = dest + ext\n }\n\n const writer = createWriteStream(dest)\n await new Promise<void>((resolve, reject) => {\n writer.once('open', () => resolve())\n writer.once('error', reject)\n })\n let streamedBytes = 0\n const limiter = new Transform({\n transform(chunk: Buffer, _encoding, callback) {\n const bytes = chunk.length\n if (streamedBytes + bytes > maxBytes) {\n callback(new Error(`Media asset exceeds ${maxBytes} byte limit`))\n return\n }\n if (options.consumeBytes && !options.consumeBytes(bytes)) {\n callback(new Error('Media export total byte limit exceeded'))\n return\n }\n streamedBytes += bytes\n callback(null, chunk)\n },\n })\n try {\n await pipeline(Readable.fromWeb(res.body as import('stream/web').ReadableStream), limiter, writer)\n } catch (error) {\n writer.destroy()\n rmSync(dest, { force: true })\n throw error\n }\n\n const { statSync } = await import('node:fs')\n const sizeBytes = statSync(dest).size\n\n return { savedPath: dest, sizeBytes, mimeType }\n}\n\nexport async function harvestPageMedia(\n html: string,\n pageUrl: string,\n options: MediaExtractOptions = {},\n): Promise<MediaManifest> {\n const types = options.types ?? ['image', 'video', 'audio']\n const typesSet = new Set(types)\n const staticAssets = discoverStaticMedia(html, pageUrl)\n const maxAssets = Math.max(1, Math.min(options.maxAssets ?? 100, 250))\n const allDiscovered = mergeMediaDiscovery(staticAssets, options.rendered, 2_000)\n const discovered = allDiscovered.slice(0, maxAssets)\n const totalFound = new Set([\n ...staticAssets.map(asset => asset.url),\n ...(options.rendered?.assets ?? []).map(asset => asset.url),\n ]).size\n const kept = discovered.flatMap(asset => {\n const type = asset.type ?? typeFromUrl(asset.url)\n return type && typesSet.has(type) ? [{ ...asset, type }] : []\n })\n const filteredCount = totalFound - kept.length\n const retentionLimitReached = allDiscovered.length > maxAssets\n const exhausted = options.rendered?.exhausted === true && !retentionLimitReached\n const stopReason = retentionLimitReached ? 'asset_limit' : options.rendered?.stopReason ?? 'render_unavailable'\n const warnings = retentionLimitReached\n ? [`Media discovery found ${allDiscovered.length} responsive families; only the requested ${maxAssets} records were retained.`]\n : options.rendered\n ? options.rendered.exhausted ? [] : [`Rendered media discovery stopped with ${options.rendered.stopReason}; the inventory may be incomplete.`]\n : ['Rendered media discovery was unavailable; this manifest contains static-source evidence only.']\n\n const baseManifest: Omit<MediaManifest, 'outputDir' | 'assets'> = {\n pageUrl,\n staticFound: staticAssets.length,\n renderedFound: options.rendered?.assets.length ?? 0,\n filteredCount,\n totalFound,\n completeness: exhausted ? 'complete' as const : 'partial' as const,\n exhausted,\n stopReason,\n scrollRounds: options.rendered?.scrollRounds ?? 0,\n warnings,\n artifact: null,\n }\n\n if (options.outputDir === null) {\n return {\n ...baseManifest,\n outputDir: null,\n assets: kept.map((asset, i) => ({\n ...asset,\n mimeType: null, filename: safeFilename(asset.url, i), savedPath: null, sizeBytes: null,\n sha256: null, downloadStatus: 'not_attempted' as const, downloadError: null, inlinePreview: null,\n })),\n }\n }\n\n const domain = (() => { try { return new URL(pageUrl).hostname.replace(/^www\\./, '') } catch { return 'unknown' } })()\n const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)\n const outDir = options.outputDir ?? join(homedir(), 'Downloads', 'mcp-scraper', 'media', `${stamp}-${domain}`)\n mkdirSync(outDir, { recursive: true })\n\n const assets: MediaAsset[] = []\n\n await Promise.allSettled(\n kept.map(async (asset, i) => {\n const filename = safeFilename(asset.url, i)\n try {\n const { savedPath, sizeBytes, mimeType } = await downloadAsset(asset.url, outDir, filename)\n const resolvedType = mimeType ? (typeFromMime(mimeType) ?? asset.type) : asset.type\n assets.push({\n ...asset, type: resolvedType, mimeType, filename: basename(savedPath), savedPath, sizeBytes,\n sha256: null, downloadStatus: 'downloaded', downloadError: null, inlinePreview: null,\n })\n } catch (error) {\n assets.push({\n ...asset, mimeType: null, filename, savedPath: null, sizeBytes: null, sha256: null,\n downloadStatus: 'failed', downloadError: error instanceof Error ? error.message : 'media_download_failed', inlinePreview: null,\n })\n }\n })\n )\n\n assets.sort((a, b) => {\n if (a.savedPath && !b.savedPath) return -1\n if (!a.savedPath && b.savedPath) return 1\n return a.url.localeCompare(b.url)\n })\n\n return { ...baseManifest, outputDir: outDir, assets }\n}\n","import {\n createPrivateArtifact,\n createPrivateArtifactFromStream,\n privateArtifactOwnerId,\n renewPrivateArtifactDownload,\n readPrivateArtifactBuffer,\n type PrivateArtifactPolicy,\n} from './private-artifacts.js'\nimport type { ExtractJobArtifact } from './site-extract-repository.js'\nimport type { Readable } from 'node:stream'\n\nexport const SITE_EXTRACT_ARTIFACT_PREFIX = 'site-extracts/'\nexport const SITE_EXTRACT_ARTIFACT_TTL_MS = 7 * 24 * 60 * 60 * 1_000\nexport const SITE_EXTRACT_DOWNLOAD_TTL_MS = 15 * 60 * 1_000\n\nfunction siteExtractArtifactToken(): string | null {\n return process.env.SITE_EXTRACT_ARTIFACT_READ_WRITE_TOKEN?.trim()\n || process.env.PRIVATE_ARTIFACT_READ_WRITE_TOKEN?.trim()\n || process.env.CONNECTED_DATA_READ_WRITE_TOKEN?.trim()\n || process.env.CONNECTED_DATA_BLOB_READ_WRITE_TOKEN?.trim()\n || null\n}\n\nfunction hostedByEnvironment(): boolean {\n return process.env.VERCEL === '1' || process.env.NODE_ENV === 'production'\n}\n\nfunction policy(): PrivateArtifactPolicy {\n return {\n prefix: SITE_EXTRACT_ARTIFACT_PREFIX,\n artifactTtlMs: SITE_EXTRACT_ARTIFACT_TTL_MS,\n downloadTtlMs: SITE_EXTRACT_DOWNLOAD_TTL_MS,\n token: siteExtractArtifactToken(),\n }\n}\n\nexport function siteExtractArtifactOwnerId(artifactId: string): string | null {\n return privateArtifactOwnerId(artifactId, SITE_EXTRACT_ARTIFACT_PREFIX)\n}\n\nexport async function createSiteExtractBundleArtifact(args: {\n ownerId: string\n jobId: string\n createdAt: Date | string | number\n content: Buffer\n}): Promise<ExtractJobArtifact> {\n const pointer = await createPrivateArtifact({\n policy: policy(),\n ownerId: args.ownerId,\n artifactKey: `${args.jobId}.zip`,\n createdAt: args.createdAt,\n filename: `${args.jobId}-site-export.zip`,\n contentType: 'application/zip',\n content: args.content,\n })\n return {\n key: pointer.artifactId,\n url: pointer.downloadUrl ?? '',\n bytes: pointer.bytes,\n contentType: pointer.contentType,\n filename: pointer.filename,\n sha256: pointer.sha256,\n expiresAt: pointer.expiresAt,\n downloadUrlExpiresAt: pointer.downloadUrlExpiresAt,\n }\n}\n\nexport async function createSiteExtractBundleArtifactStream(args: {\n ownerId: string\n jobId: string\n createdAt: Date | string | number\n content: Readable\n}): Promise<ExtractJobArtifact> {\n const pointer = await createPrivateArtifactFromStream({\n policy: policy(),\n ownerId: args.ownerId,\n artifactKey: `${args.jobId}.zip`,\n createdAt: args.createdAt,\n filename: `${args.jobId}-site-export.zip`,\n contentType: 'application/zip',\n content: args.content,\n })\n return {\n key: pointer.artifactId,\n url: pointer.downloadUrl ?? '',\n bytes: pointer.bytes,\n contentType: pointer.contentType,\n filename: pointer.filename,\n sha256: pointer.sha256,\n expiresAt: pointer.expiresAt,\n downloadUrlExpiresAt: pointer.downloadUrlExpiresAt,\n }\n}\n\nexport async function renewSiteExtractArtifactDownload(args: {\n artifactId: string\n ownerId: string\n}): Promise<{ downloadUrl: string; downloadUrlExpiresAt: string; expiresAt: string } | null> {\n return renewPrivateArtifactDownload({\n policy: policy(),\n artifactId: args.artifactId,\n ownerId: args.ownerId,\n })\n}\n\nexport async function readSiteExtractArtifactBuffer(artifactId: string): Promise<Buffer | null> {\n return readPrivateArtifactBuffer({\n policy: policy(),\n artifactId,\n maxBytes: 50 * 1024 * 1024,\n })\n}\n\n/**\n * Owner-authorized read entrypoint for site-export artifacts. Keep the ownership\n * check adjacent to the storage read so callers cannot accidentally validate an\n * ID and then dereference a different one.\n */\nexport async function readOwnedSiteExtractArtifactBuffer(args: {\n artifactId: string\n ownerId: string\n}): Promise<Buffer | null> {\n if (siteExtractArtifactOwnerId(args.artifactId) !== args.ownerId) return null\n return readSiteExtractArtifactBuffer(args.artifactId)\n}\n\nexport async function cleanupExpiredSiteExtractArtifacts(args: {\n now?: Date\n force?: boolean\n} = {}): Promise<{ deleted: number; store: 'private-vercel-blob' | 'local' | 'none'; skipped?: boolean }> {\n const now = args.now ?? new Date()\n const token = siteExtractArtifactToken()\n if (!token) return { deleted: 0, store: hostedByEnvironment() ? 'none' : 'local' }\n if (!args.force && !(now.getUTCHours() === 3 && now.getUTCMinutes() === 21)) {\n return { deleted: 0, store: 'private-vercel-blob', skipped: true }\n }\n const cutoff = now.getTime() - SITE_EXTRACT_ARTIFACT_TTL_MS\n const { list, del } = await import('@vercel/blob')\n let cursor: string | undefined\n let deleted = 0\n for (let page = 0; page < 20; page += 1) {\n const result = await list({ prefix: SITE_EXTRACT_ARTIFACT_PREFIX, token, limit: 1_000, cursor })\n const expired = result.blobs.filter(blob => new Date(blob.uploadedAt).getTime() <= cutoff)\n if (expired.length > 0) {\n await del(expired.map(blob => blob.pathname), { token })\n deleted += expired.length\n }\n if (!result.hasMore || !result.cursor) break\n cursor = result.cursor\n }\n return { deleted, store: 'private-vercel-blob' }\n}\n"],"mappings":"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","names":[]}
|