mcp-scraper 0.34.1 → 0.35.1
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 +4 -3
- package/dist/bin/api-server.cjs +3709 -2689
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +1 -1
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-install.cjs +2 -2
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +2102 -1904
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +3 -3
- package/dist/{chunk-6OPHG76G.js → chunk-NPMW5HUS.js} +2338 -2136
- package/dist/chunk-NPMW5HUS.js.map +1 -0
- package/dist/{chunk-5PZ6N2QM.js → chunk-U44TPRST.js} +2 -2
- package/dist/chunk-U44TPRST.js.map +1 -0
- package/dist/chunk-YR4LJ6AQ.js +7 -0
- package/dist/chunk-YR4LJ6AQ.js.map +1 -0
- package/dist/{chunk-7AYRWAEK.js → chunk-YRGSEY5L.js} +2 -2
- package/dist/{chunk-7AYRWAEK.js.map → chunk-YRGSEY5L.js.map} +1 -1
- package/dist/chunk-YV2FUEBX.js +851 -0
- package/dist/chunk-YV2FUEBX.js.map +1 -0
- package/dist/{extract-bundle-GPZRLBVY.js → extract-bundle-ONWZVV55.js} +59 -8
- package/dist/extract-bundle-ONWZVV55.js.map +1 -0
- package/dist/{server-IWDHTES2.js → server-GKUTC73B.js} +224 -22
- package/dist/server-GKUTC73B.js.map +1 -0
- package/dist/{site-extract-repository-OLVWMOU2.js → site-extract-repository-L6BHWVDU.js} +2 -2
- package/docs/mcp-tool-manifest.generated.json +499 -8
- package/docs/specs/query-fanout-transport-contract-fix.md +9 -1
- package/package.json +1 -1
- package/dist/chunk-5PZ6N2QM.js.map +0 -1
- package/dist/chunk-6OPHG76G.js.map +0 -1
- package/dist/chunk-7N2KYL4U.js +0 -7
- package/dist/chunk-7N2KYL4U.js.map +0 -1
- package/dist/chunk-JWIE5NCR.js +0 -284
- package/dist/chunk-JWIE5NCR.js.map +0 -1
- package/dist/extract-bundle-GPZRLBVY.js.map +0 -1
- package/dist/server-IWDHTES2.js.map +0 -1
- /package/dist/{site-extract-repository-OLVWMOU2.js.map → site-extract-repository-L6BHWVDU.js.map} +0 -0
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
expandWaybackMonths,
|
|
3
|
+
harvestPageMedia,
|
|
4
|
+
inventoryWaybackSnapshots,
|
|
5
|
+
parseWaybackReplayUrl,
|
|
6
|
+
resolveFeaturedImage,
|
|
7
|
+
resolveWaybackSiteCaptures,
|
|
8
|
+
resolveWaybackTimelineCaptures
|
|
9
|
+
} from "./chunk-YV2FUEBX.js";
|
|
4
10
|
import {
|
|
5
11
|
classifyHarvestProblem,
|
|
6
12
|
createHarvestAttemptRecorder,
|
|
@@ -23,6 +29,8 @@ import {
|
|
|
23
29
|
} from "./chunk-XVVNKASZ.js";
|
|
24
30
|
import {
|
|
25
31
|
HttpMcpToolExecutor,
|
|
32
|
+
WaybackInventoryOptionsSchema,
|
|
33
|
+
WaybackTimelineSchema,
|
|
26
34
|
assessTranscriptSignal,
|
|
27
35
|
buildLinkGraph,
|
|
28
36
|
buildPaaExtractorMcpServer,
|
|
@@ -40,7 +48,7 @@ import {
|
|
|
40
48
|
sanitizeAttempts,
|
|
41
49
|
sanitizeHarvestResult,
|
|
42
50
|
transcribeMediaUrl
|
|
43
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-NPMW5HUS.js";
|
|
44
52
|
import {
|
|
45
53
|
auditImages,
|
|
46
54
|
buildLinkReport,
|
|
@@ -80,7 +88,7 @@ import {
|
|
|
80
88
|
RawMapsOverviewSchema,
|
|
81
89
|
RawMapsReviewStatsSchema
|
|
82
90
|
} from "./chunk-CB5C3BPB.js";
|
|
83
|
-
import "./chunk-
|
|
91
|
+
import "./chunk-YR4LJ6AQ.js";
|
|
84
92
|
import {
|
|
85
93
|
completeExtractJob,
|
|
86
94
|
countSuccessfulPages,
|
|
@@ -91,7 +99,7 @@ import {
|
|
|
91
99
|
saveExtractPages,
|
|
92
100
|
setExtractJobTotal,
|
|
93
101
|
settleExtractJob
|
|
94
|
-
} from "./chunk-
|
|
102
|
+
} from "./chunk-U44TPRST.js";
|
|
95
103
|
import {
|
|
96
104
|
BROWSER_OPEN_MIN_BALANCE_MC,
|
|
97
105
|
CONCURRENCY_PRICE_ID,
|
|
@@ -7318,13 +7326,41 @@ var siteExtractFn = inngest.createFunction(
|
|
|
7318
7326
|
const maxPages = Number(job.options.maxPages ?? 1e4);
|
|
7319
7327
|
const concurrency = Number(job.options.concurrency ?? 1);
|
|
7320
7328
|
const urlsPerBrowser = Number(job.options.urlsPerBrowser ?? job.options.rotateProxyEvery ?? 10);
|
|
7329
|
+
const waybackReplay = parseWaybackReplayUrl(job.startUrl);
|
|
7330
|
+
const waybackTimeline = job.options.waybackTimeline;
|
|
7331
|
+
const disableLinkDiscovery = Boolean(waybackReplay) || Boolean(waybackTimeline);
|
|
7321
7332
|
const seedRaw = await step.run("discover", async () => {
|
|
7333
|
+
if (waybackTimeline) {
|
|
7334
|
+
const plan = await resolveWaybackTimelineCaptures({
|
|
7335
|
+
rootUrl: waybackTimeline.rootUrl,
|
|
7336
|
+
timeline: waybackTimeline.timeline,
|
|
7337
|
+
maxPagesPerSnapshot: waybackTimeline.maxPagesPerSnapshot,
|
|
7338
|
+
maxCaptures: maxPages
|
|
7339
|
+
});
|
|
7340
|
+
const urls2 = plan.captures.map((capture) => capture.rawReplayUrl);
|
|
7341
|
+
const requestedCells = plan.requestedUrls ? plan.months.length * plan.requestedUrls.length : urls2.length + plan.missing.length;
|
|
7342
|
+
await setExtractJobTotal(jobId, Math.max(urls2.length, requestedCells));
|
|
7343
|
+
return {
|
|
7344
|
+
urls: urls2,
|
|
7345
|
+
sitemapUrls: [],
|
|
7346
|
+
waybackMonthsByUrl: Object.fromEntries(
|
|
7347
|
+
plan.captures.map((capture) => [capture.rawReplayUrl, capture.requestedMonth ?? ""])
|
|
7348
|
+
)
|
|
7349
|
+
};
|
|
7350
|
+
}
|
|
7351
|
+
if (waybackReplay) {
|
|
7352
|
+
const captures = await resolveWaybackSiteCaptures(waybackReplay, maxPages);
|
|
7353
|
+
const urls2 = captures.map((capture) => capture.rawReplayUrl);
|
|
7354
|
+
await setExtractJobTotal(jobId, urls2.length);
|
|
7355
|
+
return { urls: urls2, sitemapUrls: [] };
|
|
7356
|
+
}
|
|
7322
7357
|
const discovered = await discoverUrlsWithSitemap(job.startUrl, maxPages, key);
|
|
7323
7358
|
const urls = discovered.urls.length ? discovered.urls : [job.startUrl];
|
|
7324
7359
|
await setExtractJobTotal(jobId, urls.length);
|
|
7325
7360
|
return { urls, sitemapUrls: discovered.sitemapUrls };
|
|
7326
7361
|
});
|
|
7327
|
-
const seed = Array.isArray(seedRaw) ? { urls: seedRaw, sitemapUrls: [] } : seedRaw;
|
|
7362
|
+
const seed = Array.isArray(seedRaw) ? { urls: seedRaw, sitemapUrls: [], waybackMonthsByUrl: void 0 } : seedRaw;
|
|
7363
|
+
const waybackMonthsByUrl = seed.waybackMonthsByUrl ?? {};
|
|
7328
7364
|
const siteHost = (() => {
|
|
7329
7365
|
try {
|
|
7330
7366
|
return new URL(job.startUrl).hostname.replace(/^www\./, "");
|
|
@@ -7358,7 +7394,20 @@ var siteExtractFn = inngest.createFunction(
|
|
|
7358
7394
|
if (!batch.length) break;
|
|
7359
7395
|
const newLinks = await step.run(`crawl-batch-${i}`, async () => {
|
|
7360
7396
|
const pages = await extractPagesRotating(batch, { kernelApiKey: key, concurrency, urlsPerBrowser });
|
|
7361
|
-
await saveExtractPages(jobId, pages.map((p) =>
|
|
7397
|
+
await saveExtractPages(jobId, pages.map((p) => {
|
|
7398
|
+
const replay = parseWaybackReplayUrl(p.url);
|
|
7399
|
+
return {
|
|
7400
|
+
...p,
|
|
7401
|
+
inSitemap: sitemapSet ? sitemapSet.has(norm(p.url)) : null,
|
|
7402
|
+
...replay ? {
|
|
7403
|
+
archivedUrl: p.url,
|
|
7404
|
+
archiveTimestamp: replay.timestamp,
|
|
7405
|
+
originalUrl: replay.originalUrl,
|
|
7406
|
+
requestedMonth: waybackMonthsByUrl[p.url] || void 0
|
|
7407
|
+
} : {}
|
|
7408
|
+
};
|
|
7409
|
+
}));
|
|
7410
|
+
if (disableLinkDiscovery) return [];
|
|
7362
7411
|
const found = [];
|
|
7363
7412
|
for (const p of pages) for (const l of p.outlinks ?? []) {
|
|
7364
7413
|
if (l.internal && l.href && sameSite(l.href)) found.push(l.href);
|
|
@@ -7387,7 +7436,7 @@ var siteExtractFn = inngest.createFunction(
|
|
|
7387
7436
|
return buildSeoAudit(pages, job.startUrl);
|
|
7388
7437
|
}) : null;
|
|
7389
7438
|
const artifacts = await step.run("finalize", async () => {
|
|
7390
|
-
const { assembleExtractArtifacts } = await import("./extract-bundle-
|
|
7439
|
+
const { assembleExtractArtifacts } = await import("./extract-bundle-ONWZVV55.js");
|
|
7391
7440
|
const stored = await assembleExtractArtifacts(job, { branding, imageAudit, seoAudit });
|
|
7392
7441
|
await completeExtractJob(jobId, stored);
|
|
7393
7442
|
return stored;
|
|
@@ -10374,6 +10423,7 @@ var ExtractUrlBodySchema = z14.object({
|
|
|
10374
10423
|
screenshot: z14.boolean().optional(),
|
|
10375
10424
|
screenshotDevice: z14.enum(["desktop", "mobile"]).optional(),
|
|
10376
10425
|
extractBranding: z14.boolean().optional(),
|
|
10426
|
+
includeFeaturedImage: z14.boolean().optional(),
|
|
10377
10427
|
downloadMedia: z14.boolean().optional(),
|
|
10378
10428
|
mediaTypes: z14.array(z14.enum(["image", "video", "audio"])).optional(),
|
|
10379
10429
|
allowLocal: z14.boolean().optional(),
|
|
@@ -10392,9 +10442,14 @@ var MapUrlsBodySchema = z14.object({
|
|
|
10392
10442
|
browserFallback: z14.boolean().optional(),
|
|
10393
10443
|
kernelFallback: z14.boolean().optional()
|
|
10394
10444
|
});
|
|
10445
|
+
var WaybackInventoryBodySchema = z14.object({
|
|
10446
|
+
url: z14.string().min(1, "url is required"),
|
|
10447
|
+
...WaybackInventoryOptionsSchema
|
|
10448
|
+
});
|
|
10395
10449
|
var ExtractSiteBodySchema = z14.object({
|
|
10396
10450
|
url: z14.string().min(1, "url is required"),
|
|
10397
10451
|
maxPages: z14.number().int().min(1).max(1e4).optional(),
|
|
10452
|
+
wayback: WaybackTimelineSchema.optional(),
|
|
10398
10453
|
browserFallback: z14.boolean().optional(),
|
|
10399
10454
|
kernelFallback: z14.boolean().optional(),
|
|
10400
10455
|
rotateProxies: z14.boolean().optional(),
|
|
@@ -26599,6 +26654,7 @@ function opForCostPath(p) {
|
|
|
26599
26654
|
if (p === "/extract-url") return "page_scrape";
|
|
26600
26655
|
if (p === "/extract-site") return "extract_site";
|
|
26601
26656
|
if (p === "/map-urls") return "url_map";
|
|
26657
|
+
if (p === "/wayback/snapshots") return "url_map";
|
|
26602
26658
|
if (p === "/youtube/harvest") return "yt_channel";
|
|
26603
26659
|
if (p === "/youtube/transcribe") return "yt_transcription";
|
|
26604
26660
|
if (p === "/maps/search") return "maps_search";
|
|
@@ -28313,7 +28369,7 @@ app.post("/extract-url", auth2, async (c) => {
|
|
|
28313
28369
|
const raw = await c.req.json().catch(() => ({}));
|
|
28314
28370
|
const bodyResult = ExtractUrlBodySchema.safeParse(raw);
|
|
28315
28371
|
if (!bodyResult.success) return c.json({ error: bodyResult.error.issues[0]?.message ?? "Invalid request" }, 400);
|
|
28316
|
-
const { url, screenshot: screenshot2, screenshotDevice, extractBranding: extractBranding2, downloadMedia, mediaTypes, allowLocal } = bodyResult.data;
|
|
28372
|
+
const { url, screenshot: screenshot2, screenshotDevice, extractBranding: extractBranding2, includeFeaturedImage, downloadMedia, mediaTypes, allowLocal } = bodyResult.data;
|
|
28317
28373
|
if (!allowLocal) {
|
|
28318
28374
|
const checked = await validatePublicHttpUrl(url, { field: "URL" });
|
|
28319
28375
|
if (checked.error || !checked.parsed) return c.json({ error: checked.error ?? "Invalid URL" }, 400);
|
|
@@ -28331,6 +28387,8 @@ app.post("/extract-url", auth2, async (c) => {
|
|
|
28331
28387
|
return url;
|
|
28332
28388
|
}
|
|
28333
28389
|
})();
|
|
28390
|
+
const waybackReplay = parseWaybackReplayUrl(canonicalUrl);
|
|
28391
|
+
const extractionUrl = waybackReplay?.rawReplayUrl ?? canonicalUrl;
|
|
28334
28392
|
const user = c.get("user");
|
|
28335
28393
|
const gate = await acquireConcurrencyGate(user, "extract_url", {
|
|
28336
28394
|
reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"),
|
|
@@ -28345,7 +28403,7 @@ app.post("/extract-url", auth2, async (c) => {
|
|
|
28345
28403
|
const kernelApiKey = browserServiceApiKey();
|
|
28346
28404
|
const device = screenshotDevice === "mobile" ? "mobile" : "desktop";
|
|
28347
28405
|
const [result, pageData] = await Promise.all([
|
|
28348
|
-
extractKpo({ url:
|
|
28406
|
+
extractKpo({ url: extractionUrl, kernelApiKey }),
|
|
28349
28407
|
screenshot2 || extractBranding2 ? capturePageData(canonicalUrl, { kernelApiKey, device, screenshot: !!screenshot2, branding: !!extractBranding2 }).catch((err) => {
|
|
28350
28408
|
console.error("[extract-url] capturePageData failed:", err instanceof Error ? err.message : err);
|
|
28351
28409
|
return null;
|
|
@@ -28357,15 +28415,36 @@ app.post("/extract-url", auth2, async (c) => {
|
|
|
28357
28415
|
if (screenshotBuf) {
|
|
28358
28416
|
screenshotMeta = { base64: screenshotBuf.toString("base64"), sizeBytes: screenshotBuf.length, device };
|
|
28359
28417
|
}
|
|
28360
|
-
const mediaMeta = downloadMedia ? await harvestPageMedia(result.bodyHtml, canonicalUrl, { types: mediaTypes ?? ["image", "video", "audio"], outputDir: null }) : null;
|
|
28418
|
+
const mediaMeta = downloadMedia ? await harvestPageMedia(result.bodyHtml, waybackReplay?.originalUrl ?? canonicalUrl, { types: mediaTypes ?? ["image", "video", "audio"], outputDir: null }) : null;
|
|
28419
|
+
const featuredImage = includeFeaturedImage ? resolveFeaturedImage({
|
|
28420
|
+
html: result.bodyHtml,
|
|
28421
|
+
meta: result.meta,
|
|
28422
|
+
schema: result.schema,
|
|
28423
|
+
pageUrl: waybackReplay?.rawReplayUrl ?? canonicalUrl
|
|
28424
|
+
}) : null;
|
|
28361
28425
|
const memory = bodyResult.data.depositToVault ? await persistScrapeBody(user, {
|
|
28362
28426
|
title: result.title ?? canonicalUrl,
|
|
28363
28427
|
content: result.bodyMarkdown ?? "",
|
|
28364
28428
|
source: canonicalUrl,
|
|
28365
28429
|
vault: bodyResult.data.vaultName
|
|
28366
28430
|
}) : void 0;
|
|
28431
|
+
const archive = waybackReplay ? {
|
|
28432
|
+
timestamp: waybackReplay.timestamp,
|
|
28433
|
+
originalUrl: waybackReplay.originalUrl,
|
|
28434
|
+
replayUrl: waybackReplay.replayUrl,
|
|
28435
|
+
rawReplayUrl: waybackReplay.rawReplayUrl
|
|
28436
|
+
} : null;
|
|
28367
28437
|
await logRequestEvent({ userId: user.id, source: "extract_url", status: "done", query: canonicalUrl, resultCount: result.headings.length, result });
|
|
28368
|
-
return c.json({
|
|
28438
|
+
return c.json({
|
|
28439
|
+
...result,
|
|
28440
|
+
url: canonicalUrl,
|
|
28441
|
+
archive,
|
|
28442
|
+
featuredImage,
|
|
28443
|
+
screenshot: screenshotMeta,
|
|
28444
|
+
branding: brandingData,
|
|
28445
|
+
media: mediaMeta,
|
|
28446
|
+
memory
|
|
28447
|
+
});
|
|
28369
28448
|
} catch (err) {
|
|
28370
28449
|
const msg = err instanceof Error ? err.message : String(err);
|
|
28371
28450
|
if (debited) await creditMc(user.id, MC_COSTS.page_scrape, LedgerOperation.EXTRACT_URL, "failed call");
|
|
@@ -28509,6 +28588,76 @@ app.post("/map-urls", auth2, async (c) => {
|
|
|
28509
28588
|
await releaseConcurrencyGate(gate.lockId);
|
|
28510
28589
|
}
|
|
28511
28590
|
});
|
|
28591
|
+
app.post("/wayback/snapshots", auth2, async (c) => {
|
|
28592
|
+
const raw = await c.req.json().catch(() => ({}));
|
|
28593
|
+
const bodyResult = WaybackInventoryBodySchema.safeParse(raw);
|
|
28594
|
+
if (!bodyResult.success) return c.json({ error: bodyResult.error.issues[0]?.message ?? "Invalid request" }, 400);
|
|
28595
|
+
const body = bodyResult.data;
|
|
28596
|
+
const checked = await validatePublicHttpUrl(body.url, { field: "URL" });
|
|
28597
|
+
if (checked.error || !checked.parsed) return c.json({ error: checked.error ?? "Invalid URL" }, 400);
|
|
28598
|
+
for (const selectedUrl of body.urls ?? []) {
|
|
28599
|
+
const selected = await validatePublicHttpUrl(selectedUrl, { field: "Wayback selected URL" });
|
|
28600
|
+
if (selected.error || !selected.parsed) return c.json({ error: selected.error ?? "Invalid Wayback selected URL" }, 400);
|
|
28601
|
+
}
|
|
28602
|
+
const user = c.get("user");
|
|
28603
|
+
const gate = await acquireConcurrencyGate(user, "wayback_inventory", {
|
|
28604
|
+
reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"),
|
|
28605
|
+
metadata: { url: checked.parsed.href }
|
|
28606
|
+
});
|
|
28607
|
+
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
28608
|
+
let debited = false;
|
|
28609
|
+
try {
|
|
28610
|
+
const { ok, balance_mc: balanceMc } = await debitMc(
|
|
28611
|
+
user.id,
|
|
28612
|
+
MC_COSTS.url_map,
|
|
28613
|
+
LedgerOperation.URL_MAP,
|
|
28614
|
+
checked.parsed.hostname
|
|
28615
|
+
);
|
|
28616
|
+
if (!ok) return c.json(insufficientBalanceResponse(balanceMc, MC_COSTS.url_map), 402);
|
|
28617
|
+
debited = true;
|
|
28618
|
+
const result = await inventoryWaybackSnapshots({
|
|
28619
|
+
url: checked.parsed.href,
|
|
28620
|
+
scope: body.scope,
|
|
28621
|
+
urls: body.urls,
|
|
28622
|
+
from: body.from,
|
|
28623
|
+
to: body.to,
|
|
28624
|
+
successfulHtmlOnly: body.successfulHtmlOnly,
|
|
28625
|
+
maxCaptures: body.maxCaptures,
|
|
28626
|
+
includeCaptures: body.includeCaptures,
|
|
28627
|
+
maxCaptureRows: body.maxCaptureRows
|
|
28628
|
+
});
|
|
28629
|
+
await logRequestEvent({
|
|
28630
|
+
userId: user.id,
|
|
28631
|
+
source: "wayback_inventory",
|
|
28632
|
+
status: "done",
|
|
28633
|
+
query: result.url,
|
|
28634
|
+
resultCount: result.totalCaptures,
|
|
28635
|
+
result: {
|
|
28636
|
+
scope: result.scope,
|
|
28637
|
+
from: result.from,
|
|
28638
|
+
to: result.to,
|
|
28639
|
+
totalCaptures: result.totalCaptures,
|
|
28640
|
+
countType: result.countType,
|
|
28641
|
+
uniqueUrls: result.uniqueUrls,
|
|
28642
|
+
uniqueDigests: result.uniqueDigests
|
|
28643
|
+
}
|
|
28644
|
+
});
|
|
28645
|
+
return c.json(result);
|
|
28646
|
+
} catch (error) {
|
|
28647
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
28648
|
+
if (debited) await creditMc(user.id, MC_COSTS.url_map, LedgerOperation.URL_MAP_REFUND, "failed call");
|
|
28649
|
+
await logRequestEvent({
|
|
28650
|
+
userId: user.id,
|
|
28651
|
+
source: "wayback_inventory",
|
|
28652
|
+
status: "failed",
|
|
28653
|
+
query: checked.parsed.href,
|
|
28654
|
+
error: message
|
|
28655
|
+
});
|
|
28656
|
+
return c.json({ error: message }, 500);
|
|
28657
|
+
} finally {
|
|
28658
|
+
await releaseConcurrencyGate(gate.lockId);
|
|
28659
|
+
}
|
|
28660
|
+
});
|
|
28512
28661
|
app.post("/extract-site", auth2, async (c) => {
|
|
28513
28662
|
const raw = await c.req.json().catch(() => ({}));
|
|
28514
28663
|
const bodyResult = ExtractSiteBodySchema.safeParse(raw);
|
|
@@ -28518,24 +28667,58 @@ app.post("/extract-site", auth2, async (c) => {
|
|
|
28518
28667
|
if (checked.error || !checked.parsed) return c.json({ error: checked.error ?? "Invalid URL" }, 400);
|
|
28519
28668
|
const parsed = checked.parsed;
|
|
28520
28669
|
const user = c.get("user");
|
|
28670
|
+
const waybackReplay = parseWaybackReplayUrl(parsed.href);
|
|
28671
|
+
const waybackMonths = body.wayback ? expandWaybackMonths(body.wayback) : [];
|
|
28672
|
+
const waybackRootUrl = waybackReplay?.originalUrl ?? parsed.href;
|
|
28673
|
+
if (body.wayback?.urls) {
|
|
28674
|
+
const rootHost = new URL(waybackRootUrl).hostname.replace(/^www\./, "").toLowerCase();
|
|
28675
|
+
for (const selectedUrl of body.wayback.urls) {
|
|
28676
|
+
const selected = await validatePublicHttpUrl(selectedUrl, { field: "Wayback selected URL" });
|
|
28677
|
+
if (selected.error || !selected.parsed) return c.json({ error: selected.error ?? "Invalid Wayback selected URL" }, 400);
|
|
28678
|
+
if (selected.parsed.hostname.replace(/^www\./, "").toLowerCase() !== rootHost) {
|
|
28679
|
+
return c.json({ error: "Every selected Wayback URL must belong to the same site as url." }, 400);
|
|
28680
|
+
}
|
|
28681
|
+
}
|
|
28682
|
+
}
|
|
28683
|
+
const maxPagesPerSnapshot = Math.min(500, Math.max(1, body.maxPages ?? 100));
|
|
28684
|
+
const requestedMaxPages = body.wayback ? Math.min(
|
|
28685
|
+
1e4,
|
|
28686
|
+
waybackMonths.length * Math.min(maxPagesPerSnapshot, body.wayback.urls?.length ?? maxPagesPerSnapshot)
|
|
28687
|
+
) : Math.min(1e4, Math.max(1, body.maxPages ?? 1e4));
|
|
28688
|
+
const crawlStartUrl = body.wayback ? waybackRootUrl : waybackReplay?.rawReplayUrl ?? parsed.href;
|
|
28521
28689
|
const downloadImages = body.downloadImages === true;
|
|
28522
|
-
if (body.background === true || downloadImages) {
|
|
28690
|
+
if (body.background === true || downloadImages || Boolean(body.wayback)) {
|
|
28523
28691
|
const affordablePages = Math.floor(user.balance_mc / MC_COSTS.page_scrape);
|
|
28524
28692
|
if (affordablePages < 1) return c.json(insufficientBalanceResponse(user.balance_mc, MC_COSTS.page_scrape), 402);
|
|
28525
|
-
const maxPages = Math.min(
|
|
28693
|
+
const maxPages = Math.min(requestedMaxPages, affordablePages);
|
|
28526
28694
|
const heldMc = maxPages * MC_COSTS.page_scrape;
|
|
28527
28695
|
const { ok: holdOk, balance_mc: holdBal } = await debitMc(user.id, heldMc, LedgerOperation.EXTRACT_SITE_HOLD, parsed.hostname);
|
|
28528
28696
|
if (!holdOk) return c.json(insufficientBalanceResponse(holdBal, heldMc), 402);
|
|
28529
28697
|
const jobId = `ext_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
28530
28698
|
let jobCreated = false;
|
|
28531
28699
|
try {
|
|
28532
|
-
await createExtractJob(jobId, user.id,
|
|
28700
|
+
await createExtractJob(jobId, user.id, crawlStartUrl, {
|
|
28533
28701
|
maxPages,
|
|
28534
28702
|
concurrency: concurrencyLimitForUser(user),
|
|
28535
28703
|
urlsPerBrowser: body.rotateProxyEvery ?? 10,
|
|
28536
28704
|
formats: body.formats,
|
|
28537
28705
|
downloadImages,
|
|
28538
|
-
heldMc
|
|
28706
|
+
heldMc,
|
|
28707
|
+
...waybackReplay && !body.wayback ? {
|
|
28708
|
+
waybackReplay: {
|
|
28709
|
+
timestamp: waybackReplay.timestamp,
|
|
28710
|
+
originalUrl: waybackReplay.originalUrl,
|
|
28711
|
+
replayUrl: waybackReplay.replayUrl,
|
|
28712
|
+
rawReplayUrl: waybackReplay.rawReplayUrl
|
|
28713
|
+
}
|
|
28714
|
+
} : {},
|
|
28715
|
+
...body.wayback ? {
|
|
28716
|
+
waybackTimeline: {
|
|
28717
|
+
rootUrl: waybackRootUrl,
|
|
28718
|
+
timeline: body.wayback,
|
|
28719
|
+
maxPagesPerSnapshot
|
|
28720
|
+
}
|
|
28721
|
+
} : {}
|
|
28539
28722
|
});
|
|
28540
28723
|
jobCreated = true;
|
|
28541
28724
|
await inngest.send({ name: "mcp-scraper/extract.requested", data: { jobId } });
|
|
@@ -28569,9 +28752,11 @@ app.post("/extract-site", auth2, async (c) => {
|
|
|
28569
28752
|
debited = true;
|
|
28570
28753
|
const rotateProxies = body.rotateProxies === true;
|
|
28571
28754
|
const wantsBranding = body.formats?.includes("branding") === true;
|
|
28755
|
+
const waybackCaptures = waybackReplay ? await resolveWaybackSiteCaptures(waybackReplay, siteMaxPages) : null;
|
|
28572
28756
|
const result = await extractSite({
|
|
28573
|
-
startUrl:
|
|
28757
|
+
startUrl: crawlStartUrl,
|
|
28574
28758
|
maxPages: siteMaxPages,
|
|
28759
|
+
seedUrls: waybackCaptures?.map((capture) => capture.rawReplayUrl),
|
|
28575
28760
|
kernelApiKey: rotateProxies || wantsBranding || body.browserFallback || body.kernelFallback ? browserServiceApiKey() : void 0,
|
|
28576
28761
|
formats: body.formats,
|
|
28577
28762
|
...rotateProxies ? {
|
|
@@ -28579,8 +28764,25 @@ app.post("/extract-site", auth2, async (c) => {
|
|
|
28579
28764
|
parallelism: concurrencyLimitForUser(user)
|
|
28580
28765
|
} : {}
|
|
28581
28766
|
});
|
|
28767
|
+
if (waybackReplay) {
|
|
28768
|
+
result.startUrl = waybackReplay.originalUrl;
|
|
28769
|
+
result.pages = result.pages.map((page) => {
|
|
28770
|
+
const replay = parseWaybackReplayUrl(page.url);
|
|
28771
|
+
return replay ? {
|
|
28772
|
+
...page,
|
|
28773
|
+
archivedUrl: page.url,
|
|
28774
|
+
archiveTimestamp: replay.timestamp,
|
|
28775
|
+
originalUrl: replay.originalUrl,
|
|
28776
|
+
url: replay.originalUrl,
|
|
28777
|
+
finalUrl: replay.originalUrl
|
|
28778
|
+
} : page;
|
|
28779
|
+
});
|
|
28780
|
+
}
|
|
28582
28781
|
const pageCount = result.pages?.length ?? 1;
|
|
28583
|
-
const
|
|
28782
|
+
const successfulPageCount = result.pages.filter(
|
|
28783
|
+
(page) => page.status != null && page.status >= 200 && page.status < 300 && page.wordCount > 0
|
|
28784
|
+
).length;
|
|
28785
|
+
const actualSiteMc = successfulPageCount * MC_COSTS.page_scrape;
|
|
28584
28786
|
const siteDiff = siteHoldMc - actualSiteMc;
|
|
28585
28787
|
if (siteDiff > 0) await creditMc(user.id, siteDiff, LedgerOperation.EXTRACT_SITE_REFUND, "overestimate refund");
|
|
28586
28788
|
else if (siteDiff < 0) await debitMc(user.id, -siteDiff, LedgerOperation.EXTRACT_SITE, parsed.hostname);
|
|
@@ -28989,15 +29191,15 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
|
|
|
28989
29191
|
return c.json({ error: "Unauthorized" }, 401);
|
|
28990
29192
|
}
|
|
28991
29193
|
const jobId = c.req.param("id");
|
|
28992
|
-
const { getExtractJob: getExtractJob2, completeExtractJob: completeExtractJob2 } = await import("./site-extract-repository-
|
|
28993
|
-
const { assembleExtractArtifacts } = await import("./extract-bundle-
|
|
29194
|
+
const { getExtractJob: getExtractJob2, completeExtractJob: completeExtractJob2 } = await import("./site-extract-repository-L6BHWVDU.js");
|
|
29195
|
+
const { assembleExtractArtifacts } = await import("./extract-bundle-ONWZVV55.js");
|
|
28994
29196
|
const job = await getExtractJob2(jobId);
|
|
28995
29197
|
if (!job) return c.json({ error: "job not found" }, 404);
|
|
28996
29198
|
const stored = await assembleExtractArtifacts(job, {});
|
|
28997
29199
|
await completeExtractJob2(jobId, stored);
|
|
28998
29200
|
let settlement = "already_settled_or_refunded";
|
|
28999
29201
|
if (job.billedMc == null && job.userId != null) {
|
|
29000
|
-
const { settleExtractJob: settleExtractJob2, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-
|
|
29202
|
+
const { settleExtractJob: settleExtractJob2, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-L6BHWVDU.js");
|
|
29001
29203
|
const heldMc = Number(job.options.heldMc ?? 0);
|
|
29002
29204
|
const successful = await countSuccessfulPages2(jobId);
|
|
29003
29205
|
const usedMc = Math.min(successful * MC_COSTS.page_scrape, heldMc);
|
|
@@ -29166,4 +29368,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
29166
29368
|
export {
|
|
29167
29369
|
app
|
|
29168
29370
|
};
|
|
29169
|
-
//# sourceMappingURL=server-
|
|
29371
|
+
//# sourceMappingURL=server-GKUTC73B.js.map
|