mcp-scraper 0.18.0 → 0.20.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 +2 -2
- package/dist/bin/api-server.cjs +635 -109
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- 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 +2228 -1783
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +5 -4
- package/dist/bin/mcp-stdio-server.js.map +1 -1
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +3 -3
- package/dist/{chunk-GL4BW4CP.js → chunk-22VEGGTW.js} +21 -9
- package/dist/{chunk-GL4BW4CP.js.map → chunk-22VEGGTW.js.map} +1 -1
- package/dist/{chunk-RUGJE5EB.js → chunk-EJK25QOW.js} +2 -2
- package/dist/chunk-JWIE5NCR.js +284 -0
- package/dist/chunk-JWIE5NCR.js.map +1 -0
- package/dist/{chunk-FMEDPBIV.js → chunk-KE7KE2Q2.js} +482 -35
- package/dist/chunk-KE7KE2Q2.js.map +1 -0
- package/dist/chunk-PZB3TJWK.js +7 -0
- package/dist/chunk-PZB3TJWK.js.map +1 -0
- package/dist/{chunk-BX5RCOG5.js → chunk-Q44DN6T2.js} +2 -2
- package/dist/{chunk-BX5RCOG5.js.map → chunk-Q44DN6T2.js.map} +1 -1
- package/dist/chunk-UN7VMHZL.js +244 -0
- package/dist/chunk-UN7VMHZL.js.map +1 -0
- package/dist/{chunk-RMPPYKUV.js → chunk-XDFSLSSH.js} +7 -218
- package/dist/chunk-XDFSLSSH.js.map +1 -0
- package/dist/{chunk-HE2LQPJ2.js → chunk-ZRKFW5FB.js} +2 -2
- package/dist/{db-LIOTIWVN.js → db-YHZYG7D2.js} +2 -2
- package/dist/{extract-bundle-U4D5LW5W.js → extract-bundle-OSUPAHCE.js} +58 -5
- package/dist/extract-bundle-OSUPAHCE.js.map +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/{server-MQDCAR6I.js → server-VDWIPV7F.js} +100 -363
- package/dist/server-VDWIPV7F.js.map +1 -0
- package/dist/{site-extract-repository-NVSZH35Y.js → site-extract-repository-GWKGK46Z.js} +3 -3
- package/dist/{worker-JQTS437L.js → worker-O5PZTTPY.js} +8 -8
- package/docs/mcp-tool-manifest.generated.json +564 -30
- package/docs/specs/meta-ad-creative-media-resolution-spec.md +31 -0
- package/docs/specs/unified-credit-and-scheduled-execution-billing-spec.md +1007 -0
- package/package.json +1 -1
- package/dist/chunk-FMEDPBIV.js.map +0 -1
- package/dist/chunk-HPV4VOQX.js +0 -27
- package/dist/chunk-HPV4VOQX.js.map +0 -1
- package/dist/chunk-RLWCHLV3.js +0 -7
- package/dist/chunk-RLWCHLV3.js.map +0 -1
- package/dist/chunk-RMPPYKUV.js.map +0 -1
- package/dist/extract-bundle-U4D5LW5W.js.map +0 -1
- package/dist/server-MQDCAR6I.js.map +0 -1
- /package/dist/{chunk-RUGJE5EB.js.map → chunk-EJK25QOW.js.map} +0 -0
- /package/dist/{chunk-HE2LQPJ2.js.map → chunk-ZRKFW5FB.js.map} +0 -0
- /package/dist/{db-LIOTIWVN.js.map → db-YHZYG7D2.js.map} +0 -0
- /package/dist/{site-extract-repository-NVSZH35Y.js.map → site-extract-repository-GWKGK46Z.js.map} +0 -0
- /package/dist/{worker-JQTS437L.js.map → worker-O5PZTTPY.js.map} +0 -0
|
@@ -1,27 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
harvestPageMedia
|
|
3
|
+
} from "./chunk-JWIE5NCR.js";
|
|
1
4
|
import {
|
|
2
5
|
classifyHarvestProblem,
|
|
3
6
|
createHarvestAttemptRecorder,
|
|
4
7
|
harvestProblemResponse,
|
|
5
8
|
serializeHarvestProblem
|
|
6
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-ZRKFW5FB.js";
|
|
7
10
|
import {
|
|
8
11
|
BrowserDriver,
|
|
9
12
|
MapsSelectors,
|
|
10
13
|
buildYouTubeChannelVideosUrl,
|
|
11
|
-
currentCostContext,
|
|
12
14
|
deleteKernelProxyId,
|
|
13
15
|
harvest,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
recordKernelSession,
|
|
17
|
-
recordVendorUsage,
|
|
18
|
-
resolveKernelProxyId,
|
|
19
|
-
runWithCostContext,
|
|
20
|
-
vendorCostUsd
|
|
21
|
-
} from "./chunk-RMPPYKUV.js";
|
|
16
|
+
resolveKernelProxyId
|
|
17
|
+
} from "./chunk-XDFSLSSH.js";
|
|
22
18
|
import {
|
|
23
19
|
HttpMcpToolExecutor,
|
|
24
20
|
MemoryMcpToolExecutor,
|
|
21
|
+
assessTranscriptSignal,
|
|
25
22
|
buildLinkGraph,
|
|
26
23
|
buildPaaExtractorMcpServer,
|
|
27
24
|
cleanupExpiredConnectedDataArtifacts,
|
|
@@ -36,8 +33,9 @@ import {
|
|
|
36
33
|
registerSerpIntelligenceCaptureTools,
|
|
37
34
|
renewConnectedDataArtifactDownload,
|
|
38
35
|
sanitizeAttempts,
|
|
39
|
-
sanitizeHarvestResult
|
|
40
|
-
|
|
36
|
+
sanitizeHarvestResult,
|
|
37
|
+
transcribeMediaUrl
|
|
38
|
+
} from "./chunk-KE7KE2Q2.js";
|
|
41
39
|
import {
|
|
42
40
|
auditImages,
|
|
43
41
|
buildLinkReport,
|
|
@@ -48,8 +46,14 @@ import {
|
|
|
48
46
|
browserServiceApiKey,
|
|
49
47
|
browserServiceProfileName,
|
|
50
48
|
browserServiceProfileSaveChanges,
|
|
51
|
-
browserServiceProxyId
|
|
52
|
-
|
|
49
|
+
browserServiceProxyId,
|
|
50
|
+
currentCostContext,
|
|
51
|
+
kernelCostUsd,
|
|
52
|
+
migrateCostTelemetry,
|
|
53
|
+
recordKernelSession,
|
|
54
|
+
recordVendorUsage,
|
|
55
|
+
runWithCostContext
|
|
56
|
+
} from "./chunk-UN7VMHZL.js";
|
|
53
57
|
import {
|
|
54
58
|
csvRecords,
|
|
55
59
|
listWorkflowDefinitions,
|
|
@@ -73,7 +77,7 @@ import {
|
|
|
73
77
|
RawMapsOverviewSchema,
|
|
74
78
|
RawMapsReviewStatsSchema
|
|
75
79
|
} from "./chunk-XGIPATLV.js";
|
|
76
|
-
import "./chunk-
|
|
80
|
+
import "./chunk-PZB3TJWK.js";
|
|
77
81
|
import {
|
|
78
82
|
completeExtractJob,
|
|
79
83
|
countSuccessfulPages,
|
|
@@ -84,7 +88,7 @@ import {
|
|
|
84
88
|
saveExtractPages,
|
|
85
89
|
setExtractJobTotal,
|
|
86
90
|
settleExtractJob
|
|
87
|
-
} from "./chunk-
|
|
91
|
+
} from "./chunk-EJK25QOW.js";
|
|
88
92
|
import {
|
|
89
93
|
BROWSER_OPEN_MIN_BALANCE_MC,
|
|
90
94
|
CONCURRENCY_PRICE_ID,
|
|
@@ -190,7 +194,7 @@ import {
|
|
|
190
194
|
setUserMemoryCreds,
|
|
191
195
|
upsertPageSnapshot,
|
|
192
196
|
verifyPassword
|
|
193
|
-
} from "./chunk-
|
|
197
|
+
} from "./chunk-22VEGGTW.js";
|
|
194
198
|
|
|
195
199
|
// src/blog/registry.ts
|
|
196
200
|
var posts = [
|
|
@@ -4225,285 +4229,6 @@ async function capturePageData(url, opts = {}) {
|
|
|
4225
4229
|
}
|
|
4226
4230
|
}
|
|
4227
4231
|
|
|
4228
|
-
// src/lib/media-extractor.ts
|
|
4229
|
-
import { createWriteStream, mkdirSync } from "fs";
|
|
4230
|
-
import { homedir } from "os";
|
|
4231
|
-
import { join as join2, extname, basename } from "path";
|
|
4232
|
-
import { pipeline } from "stream/promises";
|
|
4233
|
-
import { Readable } from "stream";
|
|
4234
|
-
var AD_PATTERNS = [
|
|
4235
|
-
"doubleclick.net",
|
|
4236
|
-
"googlesyndication.com",
|
|
4237
|
-
"googletagmanager.com",
|
|
4238
|
-
"google-analytics.com",
|
|
4239
|
-
"googletagservices.com",
|
|
4240
|
-
"adservice.google",
|
|
4241
|
-
"googletag.",
|
|
4242
|
-
"pagead2.googlesyndication",
|
|
4243
|
-
"facebook.net/tr",
|
|
4244
|
-
"connect.facebook.net",
|
|
4245
|
-
"fbcdn.net/rsrc",
|
|
4246
|
-
"analytics.twitter.com",
|
|
4247
|
-
"static.ads-twitter.com",
|
|
4248
|
-
"ads.twitter.com",
|
|
4249
|
-
"t.co/i/adsct",
|
|
4250
|
-
"pixel.advertising.com",
|
|
4251
|
-
"hotjar.com",
|
|
4252
|
-
"clarity.ms",
|
|
4253
|
-
"quantserve.com",
|
|
4254
|
-
"scorecardresearch.com",
|
|
4255
|
-
"newrelic.com",
|
|
4256
|
-
"nr-data.net",
|
|
4257
|
-
"segment.io",
|
|
4258
|
-
"segment.com",
|
|
4259
|
-
"amplitude.com",
|
|
4260
|
-
"mixpanel.com",
|
|
4261
|
-
"heap.io",
|
|
4262
|
-
"fullstory.com",
|
|
4263
|
-
"moatads.com",
|
|
4264
|
-
"criteo.com",
|
|
4265
|
-
"adsrvr.org",
|
|
4266
|
-
"rubiconproject.com",
|
|
4267
|
-
"pubmatic.com",
|
|
4268
|
-
"openx.net",
|
|
4269
|
-
"appnexus.com",
|
|
4270
|
-
"amazon-adsystem.com",
|
|
4271
|
-
"media.net",
|
|
4272
|
-
"yieldmo.com",
|
|
4273
|
-
"triplelift.com",
|
|
4274
|
-
"sharethrough.com",
|
|
4275
|
-
"prebid.",
|
|
4276
|
-
"smaato.net",
|
|
4277
|
-
"indexworm.com",
|
|
4278
|
-
"casalemedia.com",
|
|
4279
|
-
"outbrain.com",
|
|
4280
|
-
"taboola.com",
|
|
4281
|
-
"revcontent.com",
|
|
4282
|
-
"mgid.com",
|
|
4283
|
-
"tawk.to",
|
|
4284
|
-
"intercom.io",
|
|
4285
|
-
"drift.com",
|
|
4286
|
-
"hs-scripts.com",
|
|
4287
|
-
"zopim.com",
|
|
4288
|
-
"livechatinc.com",
|
|
4289
|
-
"userlike.com",
|
|
4290
|
-
"onetrust.com",
|
|
4291
|
-
"cookielaw.org",
|
|
4292
|
-
"cookieinformation.com",
|
|
4293
|
-
"trustarc.com",
|
|
4294
|
-
"/ads/",
|
|
4295
|
-
"/ad/",
|
|
4296
|
-
"/banner/",
|
|
4297
|
-
"/banners/",
|
|
4298
|
-
"/pixel/",
|
|
4299
|
-
"/beacon/",
|
|
4300
|
-
"/tracking/",
|
|
4301
|
-
"/tracker/",
|
|
4302
|
-
"/remarketing/",
|
|
4303
|
-
"/conversion/",
|
|
4304
|
-
"1x1.gif",
|
|
4305
|
-
"spacer.gif",
|
|
4306
|
-
"blank.gif",
|
|
4307
|
-
"transparent.gif"
|
|
4308
|
-
];
|
|
4309
|
-
var IMAGE_EXTS = /* @__PURE__ */ new Set([".jpg", ".jpeg", ".png", ".webp", ".gif", ".avif", ".svg", ".tiff"]);
|
|
4310
|
-
var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".webm", ".mov", ".avi", ".m4v", ".ogv", ".mkv"]);
|
|
4311
|
-
var AUDIO_EXTS = /* @__PURE__ */ new Set([".mp3", ".wav", ".ogg", ".aac", ".m4a", ".flac", ".opus"]);
|
|
4312
|
-
function isAdUrl(url) {
|
|
4313
|
-
const lower = url.toLowerCase();
|
|
4314
|
-
return AD_PATTERNS.some((p) => lower.includes(p));
|
|
4315
|
-
}
|
|
4316
|
-
function isDataUri(url) {
|
|
4317
|
-
return url.startsWith("data:");
|
|
4318
|
-
}
|
|
4319
|
-
function typeFromUrl(url) {
|
|
4320
|
-
try {
|
|
4321
|
-
const ext = extname(new URL(url).pathname).toLowerCase();
|
|
4322
|
-
if (IMAGE_EXTS.has(ext)) return "image";
|
|
4323
|
-
if (VIDEO_EXTS.has(ext)) return "video";
|
|
4324
|
-
if (AUDIO_EXTS.has(ext)) return "audio";
|
|
4325
|
-
} catch {
|
|
4326
|
-
}
|
|
4327
|
-
return null;
|
|
4328
|
-
}
|
|
4329
|
-
function typeFromMime(mime) {
|
|
4330
|
-
const lower = mime.toLowerCase();
|
|
4331
|
-
if (lower.startsWith("image/")) return "image";
|
|
4332
|
-
if (lower.startsWith("video/")) return "video";
|
|
4333
|
-
if (lower.startsWith("audio/")) return "audio";
|
|
4334
|
-
return null;
|
|
4335
|
-
}
|
|
4336
|
-
function resolveUrl(raw, base) {
|
|
4337
|
-
if (!raw || isDataUri(raw)) return null;
|
|
4338
|
-
try {
|
|
4339
|
-
return new URL(raw, base).href;
|
|
4340
|
-
} catch {
|
|
4341
|
-
return null;
|
|
4342
|
-
}
|
|
4343
|
-
}
|
|
4344
|
-
function safeFilename(url, index) {
|
|
4345
|
-
try {
|
|
4346
|
-
const u = new URL(url);
|
|
4347
|
-
const base = basename(u.pathname).replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
|
|
4348
|
-
return base || `asset-${index}`;
|
|
4349
|
-
} catch {
|
|
4350
|
-
return `asset-${index}`;
|
|
4351
|
-
}
|
|
4352
|
-
}
|
|
4353
|
-
function extractMediaUrls(html, baseUrl) {
|
|
4354
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4355
|
-
const urls = [];
|
|
4356
|
-
const add = (raw) => {
|
|
4357
|
-
const resolved = resolveUrl(raw.trim(), baseUrl);
|
|
4358
|
-
if (!resolved || seen.has(resolved) || isAdUrl(resolved)) return;
|
|
4359
|
-
seen.add(resolved);
|
|
4360
|
-
urls.push(resolved);
|
|
4361
|
-
};
|
|
4362
|
-
for (const m of html.matchAll(/<img\s[^>]*>/gi)) {
|
|
4363
|
-
const tag = m[0];
|
|
4364
|
-
for (const attr of ["src", "data-src", "data-lazy-src", "data-original"]) {
|
|
4365
|
-
const v = (tag.match(new RegExp(`\\b${attr}\\s*=\\s*["']([^"']+)["']`, "i")) ?? [])[1];
|
|
4366
|
-
if (v) add(v);
|
|
4367
|
-
}
|
|
4368
|
-
const srcset = (tag.match(/\bsrcset\s*=\s*["']([^"']+)["']/i) ?? [])[1];
|
|
4369
|
-
if (srcset) {
|
|
4370
|
-
for (const part of srcset.split(",")) {
|
|
4371
|
-
const u = part.trim().split(/\s+/)[0];
|
|
4372
|
-
if (u) add(u);
|
|
4373
|
-
}
|
|
4374
|
-
}
|
|
4375
|
-
}
|
|
4376
|
-
for (const m of html.matchAll(/<source\s[^>]*>/gi)) {
|
|
4377
|
-
const tag = m[0];
|
|
4378
|
-
const src = (tag.match(/\bsrc\s*=\s*["']([^"']+)["']/i) ?? [])[1];
|
|
4379
|
-
if (src) add(src);
|
|
4380
|
-
const srcset = (tag.match(/\bsrcset\s*=\s*["']([^"']+)["']/i) ?? [])[1];
|
|
4381
|
-
if (srcset) {
|
|
4382
|
-
for (const part of srcset.split(",")) {
|
|
4383
|
-
const u = part.trim().split(/\s+/)[0];
|
|
4384
|
-
if (u) add(u);
|
|
4385
|
-
}
|
|
4386
|
-
}
|
|
4387
|
-
}
|
|
4388
|
-
for (const m of html.matchAll(/<video\s[^>]*>/gi)) {
|
|
4389
|
-
const tag = m[0];
|
|
4390
|
-
for (const attr of ["src", "poster"]) {
|
|
4391
|
-
const v = (tag.match(new RegExp(`\\b${attr}\\s*=\\s*["']([^"']+)["']`, "i")) ?? [])[1];
|
|
4392
|
-
if (v) add(v);
|
|
4393
|
-
}
|
|
4394
|
-
}
|
|
4395
|
-
for (const m of html.matchAll(/<audio\s[^>]*>/gi)) {
|
|
4396
|
-
const tag = m[0];
|
|
4397
|
-
const v = (tag.match(/\bsrc\s*=\s*["']([^"']+)["']/i) ?? [])[1];
|
|
4398
|
-
if (v) add(v);
|
|
4399
|
-
}
|
|
4400
|
-
for (const m of html.matchAll(/<meta\s[^>]*>/gi)) {
|
|
4401
|
-
const tag = m[0];
|
|
4402
|
-
const prop = (tag.match(/\b(?:property|name)\s*=\s*["']([^"']+)["']/i) ?? [])[1]?.toLowerCase();
|
|
4403
|
-
if (prop === "og:image" || prop === "twitter:image") {
|
|
4404
|
-
const content = (tag.match(/\bcontent\s*=\s*["']([^"']+)["']/i) ?? [])[1];
|
|
4405
|
-
if (content) add(content);
|
|
4406
|
-
}
|
|
4407
|
-
}
|
|
4408
|
-
for (const m of html.matchAll(/background(?:-image)?\s*:\s*url\(\s*["']?([^"')]+)["']?\s*\)/gi)) {
|
|
4409
|
-
add(m[1]);
|
|
4410
|
-
}
|
|
4411
|
-
return urls;
|
|
4412
|
-
}
|
|
4413
|
-
async function downloadAsset(url, destDir, filename) {
|
|
4414
|
-
const res = await fetch(url, {
|
|
4415
|
-
headers: { "User-Agent": "Mozilla/5.0 (compatible; ThorbitBot/1.0)" },
|
|
4416
|
-
signal: AbortSignal.timeout(15e3)
|
|
4417
|
-
});
|
|
4418
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
4419
|
-
if (!res.body) throw new Error("Empty response body");
|
|
4420
|
-
const mimeType = res.headers.get("content-type")?.split(";")[0].trim() ?? null;
|
|
4421
|
-
let dest = join2(destDir, filename);
|
|
4422
|
-
if (mimeType && !extname(filename)) {
|
|
4423
|
-
const mimeExt = {
|
|
4424
|
-
"image/jpeg": ".jpg",
|
|
4425
|
-
"image/png": ".png",
|
|
4426
|
-
"image/webp": ".webp",
|
|
4427
|
-
"image/gif": ".gif",
|
|
4428
|
-
"image/svg+xml": ".svg",
|
|
4429
|
-
"image/avif": ".avif",
|
|
4430
|
-
"video/mp4": ".mp4",
|
|
4431
|
-
"video/webm": ".webm",
|
|
4432
|
-
"audio/mpeg": ".mp3",
|
|
4433
|
-
"audio/ogg": ".ogg",
|
|
4434
|
-
"audio/wav": ".wav"
|
|
4435
|
-
};
|
|
4436
|
-
const ext = mimeExt[mimeType];
|
|
4437
|
-
if (ext) dest = dest + ext;
|
|
4438
|
-
}
|
|
4439
|
-
const writer = createWriteStream(dest);
|
|
4440
|
-
await pipeline(Readable.fromWeb(res.body), writer);
|
|
4441
|
-
const { statSync } = await import("fs");
|
|
4442
|
-
const sizeBytes = statSync(dest).size;
|
|
4443
|
-
return { savedPath: dest, sizeBytes, mimeType };
|
|
4444
|
-
}
|
|
4445
|
-
async function harvestPageMedia(html, pageUrl, options = {}) {
|
|
4446
|
-
const types = options.types ?? ["image", "video", "audio"];
|
|
4447
|
-
const typesSet = new Set(types);
|
|
4448
|
-
const rawUrls = extractMediaUrls(html, pageUrl);
|
|
4449
|
-
const totalFound = rawUrls.length;
|
|
4450
|
-
const filteredUrls = [];
|
|
4451
|
-
const kept = [];
|
|
4452
|
-
for (const url of rawUrls) {
|
|
4453
|
-
const type = typeFromUrl(url);
|
|
4454
|
-
if (!type || !typesSet.has(type)) {
|
|
4455
|
-
filteredUrls.push(url);
|
|
4456
|
-
continue;
|
|
4457
|
-
}
|
|
4458
|
-
kept.push({ url, type });
|
|
4459
|
-
}
|
|
4460
|
-
if (options.outputDir === null) {
|
|
4461
|
-
return {
|
|
4462
|
-
pageUrl,
|
|
4463
|
-
outputDir: null,
|
|
4464
|
-
assets: kept.map(({ url, type }, i) => ({
|
|
4465
|
-
url,
|
|
4466
|
-
type,
|
|
4467
|
-
mimeType: null,
|
|
4468
|
-
filename: safeFilename(url, i),
|
|
4469
|
-
savedPath: null,
|
|
4470
|
-
sizeBytes: null
|
|
4471
|
-
})),
|
|
4472
|
-
filteredCount: filteredUrls.length,
|
|
4473
|
-
totalFound
|
|
4474
|
-
};
|
|
4475
|
-
}
|
|
4476
|
-
const domain = (() => {
|
|
4477
|
-
try {
|
|
4478
|
-
return new URL(pageUrl).hostname.replace(/^www\./, "");
|
|
4479
|
-
} catch {
|
|
4480
|
-
return "unknown";
|
|
4481
|
-
}
|
|
4482
|
-
})();
|
|
4483
|
-
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
4484
|
-
const outDir = options.outputDir ?? join2(homedir(), "Downloads", "mcp-scraper", "media", `${stamp}-${domain}`);
|
|
4485
|
-
mkdirSync(outDir, { recursive: true });
|
|
4486
|
-
const assets = [];
|
|
4487
|
-
await Promise.allSettled(
|
|
4488
|
-
kept.map(async ({ url, type }, i) => {
|
|
4489
|
-
const filename = safeFilename(url, i);
|
|
4490
|
-
try {
|
|
4491
|
-
const { savedPath, sizeBytes, mimeType } = await downloadAsset(url, outDir, filename);
|
|
4492
|
-
const resolvedType = mimeType ? typeFromMime(mimeType) ?? type : type;
|
|
4493
|
-
assets.push({ url, type: resolvedType, mimeType, filename: basename(savedPath), savedPath, sizeBytes });
|
|
4494
|
-
} catch {
|
|
4495
|
-
assets.push({ url, type, mimeType: null, filename, savedPath: null, sizeBytes: null });
|
|
4496
|
-
}
|
|
4497
|
-
})
|
|
4498
|
-
);
|
|
4499
|
-
assets.sort((a, b) => {
|
|
4500
|
-
if (a.savedPath && !b.savedPath) return -1;
|
|
4501
|
-
if (!a.savedPath && b.savedPath) return 1;
|
|
4502
|
-
return a.url.localeCompare(b.url);
|
|
4503
|
-
});
|
|
4504
|
-
return { pageUrl, outputDir: outDir, assets, filteredCount: filteredUrls.length, totalFound };
|
|
4505
|
-
}
|
|
4506
|
-
|
|
4507
4232
|
// src/api/site-mapper.ts
|
|
4508
4233
|
var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4509
4234
|
"jpg",
|
|
@@ -7627,7 +7352,7 @@ var siteExtractFn = inngest.createFunction(
|
|
|
7627
7352
|
return buildSeoAudit(pages, job.startUrl);
|
|
7628
7353
|
}) : null;
|
|
7629
7354
|
const artifacts = await step.run("finalize", async () => {
|
|
7630
|
-
const { assembleExtractArtifacts } = await import("./extract-bundle-
|
|
7355
|
+
const { assembleExtractArtifacts } = await import("./extract-bundle-OSUPAHCE.js");
|
|
7631
7356
|
const stored = await assembleExtractArtifacts(job, { branding, imageAudit, seoAudit });
|
|
7632
7357
|
await completeExtractJob(jobId, stored);
|
|
7633
7358
|
return stored;
|
|
@@ -8797,41 +8522,6 @@ async function resolveYouTubeAudio(videoId) {
|
|
|
8797
8522
|
}
|
|
8798
8523
|
}
|
|
8799
8524
|
|
|
8800
|
-
// src/services/media-transcription.ts
|
|
8801
|
-
import { fal } from "@fal-ai/client";
|
|
8802
|
-
function transcriptMarkdown(title, text, chunks, durationMs) {
|
|
8803
|
-
const fmtTs2 = (s) => `${Math.floor(s / 60)}:${String(Math.floor(s % 60)).padStart(2, "0")}`;
|
|
8804
|
-
const lines = [title, "", `*Transcribed in ${(durationMs / 1e3).toFixed(1)}s*`, "", "## Full Text", "", text, ""];
|
|
8805
|
-
if (chunks.length) {
|
|
8806
|
-
lines.push("## Timestamped Segments", "");
|
|
8807
|
-
for (const ch of chunks) {
|
|
8808
|
-
lines.push(`**[${fmtTs2(ch.timestamp[0])} -> ${fmtTs2(ch.timestamp[1])}]** ${ch.text.trim()}`, "");
|
|
8809
|
-
}
|
|
8810
|
-
}
|
|
8811
|
-
return lines.join("\n");
|
|
8812
|
-
}
|
|
8813
|
-
async function transcribeMediaUrl(mediaUrl, markdownTitle = "# Media Transcript") {
|
|
8814
|
-
const startMs = Date.now();
|
|
8815
|
-
const result = await fal.subscribe("fal-ai/wizper", {
|
|
8816
|
-
input: { audio_url: mediaUrl, task: "transcribe", language: "en" },
|
|
8817
|
-
logs: false,
|
|
8818
|
-
pollInterval: 3e3
|
|
8819
|
-
});
|
|
8820
|
-
const data = result.data;
|
|
8821
|
-
const text = data.text ?? "";
|
|
8822
|
-
const chunks = data.chunks ?? [];
|
|
8823
|
-
const durationMs = Date.now() - startMs;
|
|
8824
|
-
void recordVendorUsage({ vendor: "fal_wizper", model: "fal-ai/wizper", units: durationMs / 1e3, unitType: "compute_sec" });
|
|
8825
|
-
const costUsd = vendorCostUsd("fal_wizper", durationMs / 1e3);
|
|
8826
|
-
return {
|
|
8827
|
-
text,
|
|
8828
|
-
chunks,
|
|
8829
|
-
durationMs,
|
|
8830
|
-
costUsd,
|
|
8831
|
-
markdown: transcriptMarkdown(markdownTitle, text, chunks, durationMs)
|
|
8832
|
-
};
|
|
8833
|
-
}
|
|
8834
|
-
|
|
8835
8525
|
// src/youtube/CaptionFetcher.ts
|
|
8836
8526
|
async function fetchViaYoutubeTranscript(videoId) {
|
|
8837
8527
|
try {
|
|
@@ -9134,6 +8824,7 @@ var ExtractSiteBodySchema = z11.object({
|
|
|
9134
8824
|
rotateProxies: z11.boolean().optional(),
|
|
9135
8825
|
rotateProxyEvery: z11.number().int().min(1).max(100).optional(),
|
|
9136
8826
|
background: z11.boolean().optional(),
|
|
8827
|
+
downloadImages: z11.boolean().optional(),
|
|
9137
8828
|
formats: z11.array(z11.enum(["markdown", "links", "json", "images", "branding", "issues"])).optional()
|
|
9138
8829
|
});
|
|
9139
8830
|
var YoutubeHarvestBodySchema = z11.object({
|
|
@@ -10047,11 +9738,13 @@ var FacebookAdExtractor = class {
|
|
|
10047
9738
|
if (n) nameFreq.set(n, (nameFreq.get(n) ?? 0) + 1);
|
|
10048
9739
|
}
|
|
10049
9740
|
const advertiserName = nameFreq.size ? [...nameFreq.entries()].sort((a, b) => b[1] - a[1])[0][0] : null;
|
|
9741
|
+
const matchedAdvertisers = [...nameFreq.entries()].map(([name, adCount]) => ({ name, adCount })).sort((a, b) => b.adCount - a.adCount).slice(0, 20);
|
|
10050
9742
|
return {
|
|
10051
9743
|
listingUrl,
|
|
10052
9744
|
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10053
9745
|
advertiserPageId,
|
|
10054
9746
|
advertiserName,
|
|
9747
|
+
matchedAdvertisers,
|
|
10055
9748
|
summary: {
|
|
10056
9749
|
totalAds,
|
|
10057
9750
|
activeCount,
|
|
@@ -10435,7 +10128,7 @@ async function extractFacebookOrganicVideoFromPage(page, sourceUrl, quality = "b
|
|
|
10435
10128
|
}
|
|
10436
10129
|
|
|
10437
10130
|
// src/api/facebook-ad-routes.ts
|
|
10438
|
-
import { fal
|
|
10131
|
+
import { fal } from "@fal-ai/client";
|
|
10439
10132
|
var FacebookAdBodySchema = z13.object({
|
|
10440
10133
|
url: z13.string().trim().optional(),
|
|
10441
10134
|
libraryId: z13.string().trim().optional(),
|
|
@@ -10486,6 +10179,45 @@ function buildPageIntelUrl(body, country) {
|
|
|
10486
10179
|
if (body.pageId?.trim()) return `https://www.facebook.com/ads/library/?active_status=all&ad_type=all&country=${country}&is_targeted_country=false&media_type=all&search_type=page&view_all_page_id=${body.pageId.trim()}`;
|
|
10487
10180
|
return `https://www.facebook.com/ads/library/?active_status=all&ad_type=all&country=${country}&q=${encodeURIComponent(body.query.trim())}&search_type=keyword_unordered`;
|
|
10488
10181
|
}
|
|
10182
|
+
function normalizeAdvertiserName(value) {
|
|
10183
|
+
return value.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, " ").trim().replace(/\s+/g, " ");
|
|
10184
|
+
}
|
|
10185
|
+
function advertiserTokenOverlap(query, candidate) {
|
|
10186
|
+
const queryTokens = new Set(normalizeAdvertiserName(query).split(" ").filter(Boolean));
|
|
10187
|
+
const candidateTokens = new Set(normalizeAdvertiserName(candidate).split(" ").filter(Boolean));
|
|
10188
|
+
if (queryTokens.size === 0 || candidateTokens.size === 0) return 0;
|
|
10189
|
+
let overlap = 0;
|
|
10190
|
+
for (const token of queryTokens) if (candidateTokens.has(token)) overlap++;
|
|
10191
|
+
return overlap / queryTokens.size;
|
|
10192
|
+
}
|
|
10193
|
+
function assessPageIntelMatch(body, result) {
|
|
10194
|
+
if (body.pageId) return { inputMode: "pageId", matchConfidence: "high", matchReason: "Exact Facebook page ID filter.", warnings: [] };
|
|
10195
|
+
if (body.libraryId) return { inputMode: "libraryId", matchConfidence: "high", matchReason: "Exact Ad Library archive ID filter.", warnings: [] };
|
|
10196
|
+
const query = body.query?.trim() ?? "";
|
|
10197
|
+
const normalizedQuery = normalizeAdvertiserName(query);
|
|
10198
|
+
const matches = result.matchedAdvertisers ?? [];
|
|
10199
|
+
const exact = matches.find((match) => normalizeAdvertiserName(match.name) === normalizedQuery);
|
|
10200
|
+
const top = matches[0];
|
|
10201
|
+
const topShare = top && result.summary.totalAds > 0 ? top.adCount / result.summary.totalAds : 0;
|
|
10202
|
+
const overlap = top ? advertiserTokenOverlap(query, top.name) : 0;
|
|
10203
|
+
const warnings = [];
|
|
10204
|
+
let matchConfidence;
|
|
10205
|
+
let matchReason;
|
|
10206
|
+
if (exact && exact.adCount / Math.max(1, result.summary.totalAds) >= 0.7) {
|
|
10207
|
+
matchConfidence = "high";
|
|
10208
|
+
matchReason = "The exact advertiser name accounts for most returned ads.";
|
|
10209
|
+
} else if (top && overlap >= 0.75 && topShare >= 0.5) {
|
|
10210
|
+
matchConfidence = "medium";
|
|
10211
|
+
matchReason = "The leading advertiser overlaps strongly with the query, but keyword search is not page-scoped.";
|
|
10212
|
+
} else {
|
|
10213
|
+
matchConfidence = "low";
|
|
10214
|
+
matchReason = "Keyword search did not identify one dominant exact advertiser match.";
|
|
10215
|
+
}
|
|
10216
|
+
if (matches.length > 1) warnings.push(`Keyword search mixed ${matches.length} advertiser names. Confirm the intended pageId before using these ads for performance or creative analysis.`);
|
|
10217
|
+
if (matchConfidence === "low") warnings.push("Low-specificity Ad Library match: treat these results as discovery candidates, not as verified ads from the requested brand.");
|
|
10218
|
+
warnings.push("Paused, cancelled, or otherwise inactive non-political ads may be absent from Ad Library. Use meta_ad_creative_media for connected ad-account creatives.");
|
|
10219
|
+
return { inputMode: "query", matchConfidence, matchReason, warnings };
|
|
10220
|
+
}
|
|
10489
10221
|
function configuredKernelProxy() {
|
|
10490
10222
|
return (process.env.BROWSER_SERVICE_PROXY_ID ?? process.env.KERNEL_PROXY_ID)?.trim() || void 0;
|
|
10491
10223
|
}
|
|
@@ -10582,8 +10314,10 @@ facebookAdApp.post("/page-intel", createApiKeyAuth(), async (c) => {
|
|
|
10582
10314
|
await logRequestEvent({ userId: fbUser.id, source: "facebook_page_intel", status: "failed", query: body.pageId ?? body.query ?? body.libraryId ?? "", error: "soft-block: empty result refunded" });
|
|
10583
10315
|
return c.json({ error: "soft-block: no ads returned (refunded)" }, 503);
|
|
10584
10316
|
}
|
|
10585
|
-
|
|
10586
|
-
|
|
10317
|
+
const match = assessPageIntelMatch(body, result);
|
|
10318
|
+
const output = { ...result, ...match };
|
|
10319
|
+
await logRequestEvent({ userId: fbUser.id, source: "facebook_page_intel", status: "done", query: body.pageId ?? body.query ?? body.libraryId ?? "", resultCount: result.ads.length, result: output });
|
|
10320
|
+
return c.json(output);
|
|
10587
10321
|
} catch (err) {
|
|
10588
10322
|
const msg = err instanceof Error ? err.message : String(err);
|
|
10589
10323
|
if (debited && !refunded) await creditMc(fbUser.id, MC_COSTS.fb_ad, LedgerOperation.FB_AD_REFUND, "failed call");
|
|
@@ -10615,7 +10349,7 @@ facebookAdApp.post("/transcribe", createApiKeyAuth(), async (c) => {
|
|
|
10615
10349
|
metadata: { videoUrl }
|
|
10616
10350
|
});
|
|
10617
10351
|
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
10618
|
-
|
|
10352
|
+
fal.config({ credentials: process.env.FAL_KEY });
|
|
10619
10353
|
const holdMc = MEDIA_TRANSCRIBE_HOLD_MC;
|
|
10620
10354
|
let debited = false;
|
|
10621
10355
|
try {
|
|
@@ -10659,7 +10393,7 @@ facebookAdApp.post("/video-transcribe", createApiKeyAuth(), async (c) => {
|
|
|
10659
10393
|
metadata: { url: sourceUrl.href }
|
|
10660
10394
|
});
|
|
10661
10395
|
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
10662
|
-
|
|
10396
|
+
fal.config({ credentials: process.env.FAL_KEY });
|
|
10663
10397
|
const driver = new BrowserDriver();
|
|
10664
10398
|
const holdMc = MEDIA_TRANSCRIBE_HOLD_MC;
|
|
10665
10399
|
let debited = false;
|
|
@@ -10684,7 +10418,8 @@ facebookAdApp.post("/video-transcribe", createApiKeyAuth(), async (c) => {
|
|
|
10684
10418
|
text: transcript.text,
|
|
10685
10419
|
chunks: transcript.chunks,
|
|
10686
10420
|
durationMs: transcript.durationMs,
|
|
10687
|
-
markdown: transcript.markdown
|
|
10421
|
+
markdown: transcript.markdown,
|
|
10422
|
+
transcriptSignal: assessTranscriptSignal(transcript.text, transcript.chunks, video.durationSec)
|
|
10688
10423
|
};
|
|
10689
10424
|
await logRequestEvent({ userId: fbUser.id, source: "facebook_video_transcribe", status: "done", query: sourceUrl.href, resultCount: transcript.chunks.length, result });
|
|
10690
10425
|
return c.json(result);
|
|
@@ -11018,7 +10753,7 @@ var GoogleAdsExtractor = class {
|
|
|
11018
10753
|
};
|
|
11019
10754
|
|
|
11020
10755
|
// src/api/google-ads-routes.ts
|
|
11021
|
-
import { fal as
|
|
10756
|
+
import { fal as fal2 } from "@fal-ai/client";
|
|
11022
10757
|
var GoogleAdsSearchBodySchema = z14.object({
|
|
11023
10758
|
query: z14.string().trim().min(1, "query is required"),
|
|
11024
10759
|
region: z14.string().trim().toUpperCase().length(2).optional(),
|
|
@@ -11148,7 +10883,7 @@ googleAdsApp.post("/transcribe", createApiKeyAuth(), async (c) => {
|
|
|
11148
10883
|
const user = c.get("user");
|
|
11149
10884
|
const gate = await acquireConcurrencyGate(user, "google_ads_transcribe", { reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"), metadata: { videoUrl } });
|
|
11150
10885
|
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
11151
|
-
|
|
10886
|
+
fal2.config({ credentials: process.env.FAL_KEY });
|
|
11152
10887
|
const holdMc = MEDIA_TRANSCRIBE_HOLD_MC;
|
|
11153
10888
|
let debited = false;
|
|
11154
10889
|
try {
|
|
@@ -11177,7 +10912,7 @@ import { Hono as Hono7 } from "hono";
|
|
|
11177
10912
|
import { z as z15 } from "zod";
|
|
11178
10913
|
import { mkdtempSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
|
|
11179
10914
|
import { tmpdir } from "os";
|
|
11180
|
-
import { join as
|
|
10915
|
+
import { join as join3 } from "path";
|
|
11181
10916
|
import { spawn } from "child_process";
|
|
11182
10917
|
|
|
11183
10918
|
// src/extractor/InstagramContentExtractor.ts
|
|
@@ -11775,8 +11510,8 @@ instagramApp.post("/media-download", createApiKeyAuth(), async (c) => {
|
|
|
11775
11510
|
downloads.push({ kind, url: track.url, savedPath: stored.url, sizeBytes: stored.bytes, mimeType, error: null });
|
|
11776
11511
|
const isMuxInput = muxEligible && (track.url === extraction.selectedVideoTrack.url || track.url === extraction.selectedAudioTrack.url);
|
|
11777
11512
|
if (isMuxInput) {
|
|
11778
|
-
if (!muxWorkDir) muxWorkDir = mkdtempSync(
|
|
11779
|
-
const localPath =
|
|
11513
|
+
if (!muxWorkDir) muxWorkDir = mkdtempSync(join3(tmpdir(), "ig-mux-"));
|
|
11514
|
+
const localPath = join3(muxWorkDir, trackFilename(track, index, extraction.shortcode));
|
|
11780
11515
|
writeFileSync(localPath, bytes);
|
|
11781
11516
|
muxInputs.set(track.url, localPath);
|
|
11782
11517
|
}
|
|
@@ -11785,7 +11520,7 @@ instagramApp.post("/media-download", createApiKeyAuth(), async (c) => {
|
|
|
11785
11520
|
}
|
|
11786
11521
|
}
|
|
11787
11522
|
if (muxEligible && muxWorkDir && muxInputs.has(extraction.selectedVideoTrack.url) && muxInputs.has(extraction.selectedAudioTrack.url)) {
|
|
11788
|
-
const outPath =
|
|
11523
|
+
const outPath = join3(muxWorkDir, "muxed.mp4");
|
|
11789
11524
|
const muxed = await runFfmpegMux(muxInputs.get(extraction.selectedVideoTrack.url), muxInputs.get(extraction.selectedAudioTrack.url), outPath);
|
|
11790
11525
|
if (muxed.ok) {
|
|
11791
11526
|
const stored = await store.put(`${prefix}/muxed.mp4`, readFileSync2(outPath), "video/mp4");
|
|
@@ -13696,7 +13431,7 @@ import { Hono as Hono13 } from "hono";
|
|
|
13696
13431
|
|
|
13697
13432
|
// src/directory/directory-workflow.ts
|
|
13698
13433
|
import { mkdir as mkdir2, writeFile } from "fs/promises";
|
|
13699
|
-
import { join as
|
|
13434
|
+
import { join as join4 } from "path";
|
|
13700
13435
|
import { z as z21 } from "zod";
|
|
13701
13436
|
|
|
13702
13437
|
// src/directory/location-db.ts
|
|
@@ -14032,11 +13767,11 @@ function csvRowsFor(result) {
|
|
|
14032
13767
|
return rows;
|
|
14033
13768
|
}
|
|
14034
13769
|
async function saveDirectoryCsv(result) {
|
|
14035
|
-
const outDir =
|
|
13770
|
+
const outDir = join4(outputBaseDir(), "directory-workflows");
|
|
14036
13771
|
await mkdir2(outDir, { recursive: true });
|
|
14037
13772
|
const stamp = result.extractedAt.replace(/[:.]/g, "-");
|
|
14038
13773
|
const slug = `${result.state}-${result.query}`.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
14039
|
-
const path5 =
|
|
13774
|
+
const path5 = join4(outDir, `${stamp}-${slug}-directory-workflow.csv`);
|
|
14040
13775
|
const headers = [
|
|
14041
13776
|
"source_query",
|
|
14042
13777
|
"source_location",
|
|
@@ -20608,8 +20343,8 @@ async function importServiceConnectionToMemory(identity, input, dependencies) {
|
|
|
20608
20343
|
|
|
20609
20344
|
// src/api/scrape-blob-cleanup.ts
|
|
20610
20345
|
import { readdir, stat, unlink } from "fs/promises";
|
|
20611
|
-
import { homedir
|
|
20612
|
-
import { join as
|
|
20346
|
+
import { homedir } from "os";
|
|
20347
|
+
import { join as join5 } from "path";
|
|
20613
20348
|
async function cleanupVercel(token, cutoff) {
|
|
20614
20349
|
const { list, del } = await import("@vercel/blob");
|
|
20615
20350
|
let cursor;
|
|
@@ -20626,8 +20361,8 @@ async function cleanupVercel(token, cutoff) {
|
|
|
20626
20361
|
return { deleted, store: "vercel-blob" };
|
|
20627
20362
|
}
|
|
20628
20363
|
async function cleanupLocal(cutoff) {
|
|
20629
|
-
const baseDir = process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() ||
|
|
20630
|
-
const dir =
|
|
20364
|
+
const baseDir = process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join5(homedir(), "Downloads", "mcp-scraper");
|
|
20365
|
+
const dir = join5(baseDir, "blobs", SCRAPE_FALLBACK_PREFIX.replace(/\/$/, ""));
|
|
20631
20366
|
let deleted = 0;
|
|
20632
20367
|
let entries;
|
|
20633
20368
|
try {
|
|
@@ -20636,7 +20371,7 @@ async function cleanupLocal(cutoff) {
|
|
|
20636
20371
|
return { deleted: 0, store: "local" };
|
|
20637
20372
|
}
|
|
20638
20373
|
for (const name of entries) {
|
|
20639
|
-
const path5 =
|
|
20374
|
+
const path5 = join5(dir, name);
|
|
20640
20375
|
try {
|
|
20641
20376
|
const s = await stat(path5);
|
|
20642
20377
|
if (s.isFile() && s.mtimeMs < cutoff) {
|
|
@@ -22209,7 +21944,7 @@ var auth2 = createMiddleware3(async (c, next) => {
|
|
|
22209
21944
|
const user = await getUserByApiKey(key);
|
|
22210
21945
|
if (!user) return c.json({ error: "Invalid or inactive API key" }, 401);
|
|
22211
21946
|
const refreshed = await applyMonthlyFreeRefresh(user);
|
|
22212
|
-
const balanceMc = await reconcileBalanceMc(refreshed.id);
|
|
21947
|
+
const balanceMc = await reconcileBalanceMc(refreshed.id, refreshed.balance_mc);
|
|
22213
21948
|
c.set("user", { ...refreshed, balance_mc: balanceMc });
|
|
22214
21949
|
return next();
|
|
22215
21950
|
});
|
|
@@ -22228,7 +21963,7 @@ var sessionAuth = createMiddleware3(async (c, next) => {
|
|
|
22228
21963
|
const user = await getUserById(userId);
|
|
22229
21964
|
if (!user) return c.json({ error: "User not found" }, 401);
|
|
22230
21965
|
const refreshed = await applyMonthlyFreeRefresh(user);
|
|
22231
|
-
const balanceMc = await reconcileBalanceMc(refreshed.id);
|
|
21966
|
+
const balanceMc = await reconcileBalanceMc(refreshed.id, refreshed.balance_mc);
|
|
22232
21967
|
c.set("sessionUser", { ...refreshed, balance_mc: balanceMc });
|
|
22233
21968
|
return next();
|
|
22234
21969
|
});
|
|
@@ -22468,7 +22203,7 @@ app.get("/me", async (c) => {
|
|
|
22468
22203
|
}
|
|
22469
22204
|
const refreshed = await applyMonthlyFreeRefresh(foundUser);
|
|
22470
22205
|
const [balanceMc, stats] = await Promise.all([
|
|
22471
|
-
reconcileBalanceMc(refreshed.id),
|
|
22206
|
+
reconcileBalanceMc(refreshed.id, refreshed.balance_mc),
|
|
22472
22207
|
getUserStats(refreshed.id)
|
|
22473
22208
|
]);
|
|
22474
22209
|
const user = { ...refreshed, balance_mc: balanceMc };
|
|
@@ -23892,7 +23627,8 @@ app.post("/extract-site", auth2, async (c) => {
|
|
|
23892
23627
|
if (checked.error || !checked.parsed) return c.json({ error: checked.error ?? "Invalid URL" }, 400);
|
|
23893
23628
|
const parsed = checked.parsed;
|
|
23894
23629
|
const user = c.get("user");
|
|
23895
|
-
|
|
23630
|
+
const downloadImages = body.downloadImages === true;
|
|
23631
|
+
if (body.background === true || downloadImages) {
|
|
23896
23632
|
const affordablePages = Math.floor(user.balance_mc / MC_COSTS.page_scrape);
|
|
23897
23633
|
if (affordablePages < 1) return c.json(insufficientBalanceResponse(user.balance_mc, MC_COSTS.page_scrape), 402);
|
|
23898
23634
|
const maxPages = Math.min(Math.min(1e4, Math.max(1, body.maxPages ?? 1e4)), affordablePages);
|
|
@@ -23907,6 +23643,7 @@ app.post("/extract-site", auth2, async (c) => {
|
|
|
23907
23643
|
concurrency: concurrencyLimitForUser(user),
|
|
23908
23644
|
urlsPerBrowser: body.rotateProxyEvery ?? 10,
|
|
23909
23645
|
formats: body.formats,
|
|
23646
|
+
downloadImages,
|
|
23910
23647
|
heldMc
|
|
23911
23648
|
});
|
|
23912
23649
|
jobCreated = true;
|
|
@@ -24215,7 +23952,7 @@ app.post("/billing/concurrency/cancel", requireAllowedOrigin, sessionAuth, async
|
|
|
24215
23952
|
});
|
|
24216
23953
|
app.get("/billing/balance", auth2, async (c) => {
|
|
24217
23954
|
const user = c.get("user");
|
|
24218
|
-
const balanceMc =
|
|
23955
|
+
const balanceMc = user.balance_mc;
|
|
24219
23956
|
const ledger = await getLedger(user.id, 20);
|
|
24220
23957
|
const freeCredits = await getFreeCreditBreakdown(user.id);
|
|
24221
23958
|
return c.json({
|
|
@@ -24227,7 +23964,7 @@ app.get("/billing/balance", auth2, async (c) => {
|
|
|
24227
23964
|
});
|
|
24228
23965
|
app.post("/billing/credits", auth2, async (c) => {
|
|
24229
23966
|
const user = c.get("user");
|
|
24230
|
-
const balanceMc =
|
|
23967
|
+
const balanceMc = user.balance_mc;
|
|
24231
23968
|
const body = await c.req.json().catch(() => ({}));
|
|
24232
23969
|
const query = body.item?.trim().toLowerCase();
|
|
24233
23970
|
const costs = CREDIT_COST_CATALOG.map(({ aliases, ...cost }) => cost);
|
|
@@ -24260,7 +23997,7 @@ app.get("/cron/tick", async (c) => {
|
|
|
24260
23997
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
24261
23998
|
return c.json({ error: "Unauthorized" }, 401);
|
|
24262
23999
|
}
|
|
24263
|
-
const { drainQueue } = await import("./worker-
|
|
24000
|
+
const { drainQueue } = await import("./worker-O5PZTTPY.js");
|
|
24264
24001
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
24265
24002
|
const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
|
|
24266
24003
|
const [results, sweepResult, reapResult, expiredResult, blobCleanup, connectedDataArtifactCleanup] = await Promise.all([
|
|
@@ -24288,15 +24025,15 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
|
|
|
24288
24025
|
return c.json({ error: "Unauthorized" }, 401);
|
|
24289
24026
|
}
|
|
24290
24027
|
const jobId = c.req.param("id");
|
|
24291
|
-
const { getExtractJob: getExtractJob2, completeExtractJob: completeExtractJob2 } = await import("./site-extract-repository-
|
|
24292
|
-
const { assembleExtractArtifacts } = await import("./extract-bundle-
|
|
24028
|
+
const { getExtractJob: getExtractJob2, completeExtractJob: completeExtractJob2 } = await import("./site-extract-repository-GWKGK46Z.js");
|
|
24029
|
+
const { assembleExtractArtifacts } = await import("./extract-bundle-OSUPAHCE.js");
|
|
24293
24030
|
const job = await getExtractJob2(jobId);
|
|
24294
24031
|
if (!job) return c.json({ error: "job not found" }, 404);
|
|
24295
24032
|
const stored = await assembleExtractArtifacts(job, {});
|
|
24296
24033
|
await completeExtractJob2(jobId, stored);
|
|
24297
24034
|
let settlement = "already_settled_or_refunded";
|
|
24298
24035
|
if (job.billedMc == null && job.userId != null) {
|
|
24299
|
-
const { settleExtractJob: settleExtractJob2, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-
|
|
24036
|
+
const { settleExtractJob: settleExtractJob2, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-GWKGK46Z.js");
|
|
24300
24037
|
const heldMc = Number(job.options.heldMc ?? 0);
|
|
24301
24038
|
const successful = await countSuccessfulPages2(jobId);
|
|
24302
24039
|
const usedMc = Math.min(successful * MC_COSTS.page_scrape, heldMc);
|
|
@@ -24461,4 +24198,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
24461
24198
|
export {
|
|
24462
24199
|
app
|
|
24463
24200
|
};
|
|
24464
|
-
//# sourceMappingURL=server-
|
|
24201
|
+
//# sourceMappingURL=server-VDWIPV7F.js.map
|