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.
Files changed (40) hide show
  1. package/README.md +4 -3
  2. package/dist/bin/api-server.cjs +3709 -2689
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +1 -1
  5. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  6. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  7. package/dist/bin/mcp-scraper-cli.js +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs +2 -2
  9. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  10. package/dist/bin/mcp-scraper-install.js +2 -2
  11. package/dist/bin/mcp-stdio-server.cjs +2102 -1904
  12. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  13. package/dist/bin/mcp-stdio-server.js +3 -3
  14. package/dist/{chunk-6OPHG76G.js → chunk-NPMW5HUS.js} +2338 -2136
  15. package/dist/chunk-NPMW5HUS.js.map +1 -0
  16. package/dist/{chunk-5PZ6N2QM.js → chunk-U44TPRST.js} +2 -2
  17. package/dist/chunk-U44TPRST.js.map +1 -0
  18. package/dist/chunk-YR4LJ6AQ.js +7 -0
  19. package/dist/chunk-YR4LJ6AQ.js.map +1 -0
  20. package/dist/{chunk-7AYRWAEK.js → chunk-YRGSEY5L.js} +2 -2
  21. package/dist/{chunk-7AYRWAEK.js.map → chunk-YRGSEY5L.js.map} +1 -1
  22. package/dist/chunk-YV2FUEBX.js +851 -0
  23. package/dist/chunk-YV2FUEBX.js.map +1 -0
  24. package/dist/{extract-bundle-GPZRLBVY.js → extract-bundle-ONWZVV55.js} +59 -8
  25. package/dist/extract-bundle-ONWZVV55.js.map +1 -0
  26. package/dist/{server-IWDHTES2.js → server-GKUTC73B.js} +224 -22
  27. package/dist/server-GKUTC73B.js.map +1 -0
  28. package/dist/{site-extract-repository-OLVWMOU2.js → site-extract-repository-L6BHWVDU.js} +2 -2
  29. package/docs/mcp-tool-manifest.generated.json +499 -8
  30. package/docs/specs/query-fanout-transport-contract-fix.md +9 -1
  31. package/package.json +1 -1
  32. package/dist/chunk-5PZ6N2QM.js.map +0 -1
  33. package/dist/chunk-6OPHG76G.js.map +0 -1
  34. package/dist/chunk-7N2KYL4U.js +0 -7
  35. package/dist/chunk-7N2KYL4U.js.map +0 -1
  36. package/dist/chunk-JWIE5NCR.js +0 -284
  37. package/dist/chunk-JWIE5NCR.js.map +0 -1
  38. package/dist/extract-bundle-GPZRLBVY.js.map +0 -1
  39. package/dist/server-IWDHTES2.js.map +0 -1
  40. /package/dist/{site-extract-repository-OLVWMOU2.js.map → site-extract-repository-L6BHWVDU.js.map} +0 -0
@@ -1,6 +1,12 @@
1
1
  import {
2
- harvestPageMedia
3
- } from "./chunk-JWIE5NCR.js";
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-6OPHG76G.js";
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-7N2KYL4U.js";
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-5PZ6N2QM.js";
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) => ({ ...p, inSitemap: sitemapSet ? sitemapSet.has(norm(p.url)) : null })));
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-GPZRLBVY.js");
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: canonicalUrl, kernelApiKey }),
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({ ...result, screenshot: screenshotMeta, branding: brandingData, media: mediaMeta, memory });
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(Math.min(1e4, Math.max(1, body.maxPages ?? 1e4)), affordablePages);
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, parsed.href, {
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: parsed.href,
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 actualSiteMc = pageCount * MC_COSTS.page_scrape;
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-OLVWMOU2.js");
28993
- const { assembleExtractArtifacts } = await import("./extract-bundle-GPZRLBVY.js");
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-OLVWMOU2.js");
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-IWDHTES2.js.map
29371
+ //# sourceMappingURL=server-GKUTC73B.js.map