mcp-scraper 0.4.2 → 0.4.4
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/dist/bin/api-server.cjs +2386 -1816
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- 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 +1 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +1 -1
- package/dist/bin/mcp-stdio-server.cjs +164 -1
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/{chunk-NVUBFCJK.js → chunk-4ZZWFI6L.js} +2 -2
- package/dist/{chunk-4MFYMUIF.js → chunk-5LO6EHEH.js} +25 -3
- package/dist/chunk-5LO6EHEH.js.map +1 -0
- package/dist/chunk-TZRPP45I.js +7 -0
- package/dist/chunk-TZRPP45I.js.map +1 -0
- package/dist/{chunk-GFY6XHZS.js → chunk-Z34NGK64.js} +165 -2
- package/dist/chunk-Z34NGK64.js.map +1 -0
- package/dist/{server-6IYQQK6X.js → server-IKT3QVFB.js} +556 -198
- package/dist/server-IKT3QVFB.js.map +1 -0
- package/dist/{site-extract-repository-4NVO5KQA.js → site-extract-repository-THBVEXMP.js} +3 -3
- package/dist/{worker-7XRZNLKB.js → worker-YHKUJR5P.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-4MFYMUIF.js.map +0 -1
- package/dist/chunk-7HICT4GY.js +0 -7
- package/dist/chunk-7HICT4GY.js.map +0 -1
- package/dist/chunk-GFY6XHZS.js.map +0 -1
- package/dist/server-6IYQQK6X.js.map +0 -1
- /package/dist/{chunk-NVUBFCJK.js.map → chunk-4ZZWFI6L.js.map} +0 -0
- /package/dist/{site-extract-repository-4NVO5KQA.js.map → site-extract-repository-THBVEXMP.js.map} +0 -0
- /package/dist/{worker-7XRZNLKB.js.map → worker-YHKUJR5P.js.map} +0 -0
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
registerSerpIntelligenceCaptureTools,
|
|
32
32
|
sanitizeAttempts,
|
|
33
33
|
sanitizeHarvestResult
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-Z34NGK64.js";
|
|
35
35
|
import {
|
|
36
36
|
auditImages,
|
|
37
37
|
getBlobStore
|
|
@@ -65,7 +65,7 @@ import {
|
|
|
65
65
|
RawMapsOverviewSchema,
|
|
66
66
|
RawMapsReviewStatsSchema
|
|
67
67
|
} from "./chunk-XGIPATLV.js";
|
|
68
|
-
import "./chunk-
|
|
68
|
+
import "./chunk-TZRPP45I.js";
|
|
69
69
|
import {
|
|
70
70
|
completeExtractJob,
|
|
71
71
|
countSuccessfulPages,
|
|
@@ -76,7 +76,7 @@ import {
|
|
|
76
76
|
saveExtractPages,
|
|
77
77
|
setExtractJobTotal,
|
|
78
78
|
settleExtractJob
|
|
79
|
-
} from "./chunk-
|
|
79
|
+
} from "./chunk-4ZZWFI6L.js";
|
|
80
80
|
import {
|
|
81
81
|
BROWSER_OPEN_MIN_BALANCE_MC,
|
|
82
82
|
CONCURRENCY_PRICE_ID,
|
|
@@ -95,7 +95,7 @@ import {
|
|
|
95
95
|
browserActiveCostMc,
|
|
96
96
|
concurrencySlotBillingInfo,
|
|
97
97
|
insufficientBalanceResponse
|
|
98
|
-
} from "./chunk-
|
|
98
|
+
} from "./chunk-5LO6EHEH.js";
|
|
99
99
|
import {
|
|
100
100
|
CaptchaError,
|
|
101
101
|
RECAPTCHA_INSTRUCTIONS,
|
|
@@ -5274,7 +5274,7 @@ async function extractSite(opts) {
|
|
|
5274
5274
|
}
|
|
5275
5275
|
|
|
5276
5276
|
// src/api/server.ts
|
|
5277
|
-
import { Hono as
|
|
5277
|
+
import { Hono as Hono22 } from "hono";
|
|
5278
5278
|
import { serve as serveInngest } from "inngest/hono";
|
|
5279
5279
|
|
|
5280
5280
|
// src/inngest/client.ts
|
|
@@ -13160,13 +13160,369 @@ mapsApp.post("/place", createApiKeyAuth(), async (c) => {
|
|
|
13160
13160
|
}
|
|
13161
13161
|
});
|
|
13162
13162
|
|
|
13163
|
-
// src/api/
|
|
13163
|
+
// src/api/trustpilot-routes.ts
|
|
13164
13164
|
import { Hono as Hono11 } from "hono";
|
|
13165
|
+
import { z as z19 } from "zod";
|
|
13166
|
+
var TrustpilotReviewsBodySchema = z19.object({
|
|
13167
|
+
domain: z19.string().trim().min(1, "domain is required"),
|
|
13168
|
+
maxPages: z19.number().int().min(1).max(50).default(5)
|
|
13169
|
+
});
|
|
13170
|
+
function invalidRequest6(message) {
|
|
13171
|
+
return { error_code: "invalid_request", message };
|
|
13172
|
+
}
|
|
13173
|
+
function normalizeDomain(input) {
|
|
13174
|
+
return input.trim().replace(/^https?:\/\//i, "").replace(/\/.*$/, "");
|
|
13175
|
+
}
|
|
13176
|
+
function reviewPageUrl(domain, page) {
|
|
13177
|
+
const base = `https://www.trustpilot.com/review/${encodeURIComponent(domain)}`;
|
|
13178
|
+
return page <= 1 ? base : `${base}?page=${page}`;
|
|
13179
|
+
}
|
|
13180
|
+
async function extractReviewCards(page) {
|
|
13181
|
+
return page.evaluate(() => {
|
|
13182
|
+
const cards = Array.from(document.querySelectorAll('article[data-service-review-card-paper="true"]')).filter((a) => a.querySelector('[data-testid="service-review-card-v2"]'));
|
|
13183
|
+
return cards.map((card) => {
|
|
13184
|
+
const profileLink = card.querySelector('a[data-consumer-profile-link="true"]');
|
|
13185
|
+
const profileHref = profileLink?.getAttribute("href") ?? "";
|
|
13186
|
+
const profileId = profileHref.startsWith("/users/") ? profileHref.slice("/users/".length) : null;
|
|
13187
|
+
const name = card.querySelector('[data-consumer-name-typography="true"]')?.textContent?.trim() ?? null;
|
|
13188
|
+
const timeEl = card.querySelector("time[datetime]");
|
|
13189
|
+
const date = timeEl?.getAttribute("datetime") ?? null;
|
|
13190
|
+
const ratingEl = card.querySelector("[data-service-review-rating]");
|
|
13191
|
+
const ratingRaw = ratingEl?.getAttribute("data-service-review-rating");
|
|
13192
|
+
const rating = ratingRaw ? parseInt(ratingRaw, 10) : null;
|
|
13193
|
+
const titleLink = card.querySelector('a[data-review-title-typography="true"]');
|
|
13194
|
+
const titleHref = titleLink?.getAttribute("href") ?? "";
|
|
13195
|
+
const reviewId = titleHref.startsWith("/reviews/") ? titleHref.slice("/reviews/".length) : null;
|
|
13196
|
+
const title = card.querySelector('[data-service-review-title-typography="true"]')?.textContent?.trim() ?? null;
|
|
13197
|
+
const bodyEl = card.querySelector('[data-service-review-text-typography="true"]');
|
|
13198
|
+
const rawBody = bodyEl?.textContent?.trim() ?? "";
|
|
13199
|
+
const seeMoreEl = card.querySelector('[class*="seeMore"]');
|
|
13200
|
+
const truncated = !!seeMoreEl;
|
|
13201
|
+
const body = truncated ? rawBody.replace(/see more\s*$/i, "").trim() : rawBody;
|
|
13202
|
+
const labelEl = card.querySelector('[class*="reviewLabel"]');
|
|
13203
|
+
const labelText = labelEl?.textContent?.trim() ?? null;
|
|
13204
|
+
const origin = labelText && /invit/i.test(labelText) ? "invited" : labelText ? labelText.toLowerCase() : "organic";
|
|
13205
|
+
const companyReplied = !!card.querySelector('[class*="companyReply"]');
|
|
13206
|
+
return {
|
|
13207
|
+
source: "trustpilot",
|
|
13208
|
+
sourceReviewId: reviewId,
|
|
13209
|
+
reviewUrl: reviewId ? `https://www.trustpilot.com/reviews/${reviewId}` : null,
|
|
13210
|
+
reviewer: {
|
|
13211
|
+
name,
|
|
13212
|
+
profileId,
|
|
13213
|
+
title: null,
|
|
13214
|
+
companySegment: null
|
|
13215
|
+
},
|
|
13216
|
+
rating,
|
|
13217
|
+
ratingScale: 5,
|
|
13218
|
+
title,
|
|
13219
|
+
date,
|
|
13220
|
+
body: [{ question: null, answer: body }],
|
|
13221
|
+
truncated,
|
|
13222
|
+
flags: {
|
|
13223
|
+
origin,
|
|
13224
|
+
incentivized: null,
|
|
13225
|
+
validated: null,
|
|
13226
|
+
currentUser: null,
|
|
13227
|
+
companyReplied
|
|
13228
|
+
}
|
|
13229
|
+
};
|
|
13230
|
+
});
|
|
13231
|
+
});
|
|
13232
|
+
}
|
|
13233
|
+
var trustpilotApp = new Hono11();
|
|
13234
|
+
trustpilotApp.post("/reviews", createApiKeyAuth(), async (c) => {
|
|
13235
|
+
const raw = await c.req.json().catch(() => ({}));
|
|
13236
|
+
const parsed = TrustpilotReviewsBodySchema.safeParse(raw);
|
|
13237
|
+
if (!parsed.success) return c.json(invalidRequest6(parsed.error.issues[0]?.message ?? "Invalid request"), 400);
|
|
13238
|
+
const body = parsed.data;
|
|
13239
|
+
const domain = normalizeDomain(body.domain);
|
|
13240
|
+
const user = c.get("user");
|
|
13241
|
+
const gate = await acquireConcurrencyGate(user, "trustpilot_reviews", {
|
|
13242
|
+
reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"),
|
|
13243
|
+
metadata: { domain, maxPages: body.maxPages }
|
|
13244
|
+
});
|
|
13245
|
+
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
13246
|
+
let debited = false;
|
|
13247
|
+
let reviewDebitMc = 0;
|
|
13248
|
+
try {
|
|
13249
|
+
const { ok, balance_mc } = await debitMc(user.id, MC_COSTS.trustpilot_reviews, LedgerOperation.TRUSTPILOT_REVIEWS, domain);
|
|
13250
|
+
if (!ok) return c.json(insufficientBalanceResponse(balance_mc, MC_COSTS.trustpilot_reviews), 402);
|
|
13251
|
+
debited = true;
|
|
13252
|
+
const startMs = Date.now();
|
|
13253
|
+
const driver = new BrowserDriver();
|
|
13254
|
+
const allReviews = [];
|
|
13255
|
+
let pagesFetched = 0;
|
|
13256
|
+
try {
|
|
13257
|
+
await driver.launch({
|
|
13258
|
+
headless: true,
|
|
13259
|
+
kernelApiKey: browserServiceApiKey(),
|
|
13260
|
+
viewport: { width: 1280, height: 900 },
|
|
13261
|
+
locale: "en-US"
|
|
13262
|
+
});
|
|
13263
|
+
const page = driver.getPage();
|
|
13264
|
+
for (let pageNum = 1; pageNum <= body.maxPages; pageNum++) {
|
|
13265
|
+
const url = reviewPageUrl(domain, pageNum);
|
|
13266
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 45e3 });
|
|
13267
|
+
await page.waitForTimeout(2e3);
|
|
13268
|
+
const cards = await extractReviewCards(page);
|
|
13269
|
+
pagesFetched = pageNum;
|
|
13270
|
+
if (cards.length === 0) break;
|
|
13271
|
+
allReviews.push(...cards);
|
|
13272
|
+
}
|
|
13273
|
+
} finally {
|
|
13274
|
+
await driver.close();
|
|
13275
|
+
}
|
|
13276
|
+
if (pagesFetched === 0 || allReviews.length === 0) {
|
|
13277
|
+
await creditMc(user.id, MC_COSTS.trustpilot_reviews, LedgerOperation.TRUSTPILOT_REVIEWS_REFUND, "no reviews found");
|
|
13278
|
+
await logRequestEvent({ userId: user.id, source: "trustpilot_reviews", status: "failed", query: domain, error: "no reviews found" });
|
|
13279
|
+
return c.json({ error: "No reviews found for this domain (refunded)", domain }, 404);
|
|
13280
|
+
}
|
|
13281
|
+
reviewDebitMc = allReviews.length * MC_COSTS.trustpilot_review;
|
|
13282
|
+
if (reviewDebitMc > 0) {
|
|
13283
|
+
const reviewDebit = await debitMc(user.id, reviewDebitMc, LedgerOperation.TRUSTPILOT_REVIEW, domain);
|
|
13284
|
+
if (!reviewDebit.ok) {
|
|
13285
|
+
allReviews.length = Math.floor((reviewDebit.balance_mc ?? 0) / MC_COSTS.trustpilot_review);
|
|
13286
|
+
reviewDebitMc = allReviews.length * MC_COSTS.trustpilot_review;
|
|
13287
|
+
if (reviewDebitMc > 0) await debitMc(user.id, reviewDebitMc, LedgerOperation.TRUSTPILOT_REVIEW, domain);
|
|
13288
|
+
}
|
|
13289
|
+
}
|
|
13290
|
+
const result = {
|
|
13291
|
+
domain,
|
|
13292
|
+
reviewUrl: reviewPageUrl(domain, 1),
|
|
13293
|
+
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13294
|
+
requestedMaxPages: body.maxPages,
|
|
13295
|
+
pagesFetched,
|
|
13296
|
+
reviewCount: allReviews.length,
|
|
13297
|
+
reviews: allReviews,
|
|
13298
|
+
durationMs: Date.now() - startMs
|
|
13299
|
+
};
|
|
13300
|
+
await logRequestEvent({ userId: user.id, source: "trustpilot_reviews", status: "done", query: domain, resultCount: allReviews.length, result });
|
|
13301
|
+
return c.json(result);
|
|
13302
|
+
} catch (err) {
|
|
13303
|
+
if (debited) await creditMc(user.id, MC_COSTS.trustpilot_reviews, LedgerOperation.TRUSTPILOT_REVIEWS_REFUND, "failed call");
|
|
13304
|
+
if (reviewDebitMc > 0) await creditMc(user.id, reviewDebitMc, LedgerOperation.TRUSTPILOT_REVIEW_REFUND, "failed call");
|
|
13305
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
13306
|
+
await logRequestEvent({ userId: user.id, source: "trustpilot_reviews", status: "failed", query: domain, error: msg });
|
|
13307
|
+
return c.json({ error: msg }, 500);
|
|
13308
|
+
} finally {
|
|
13309
|
+
await releaseConcurrencyGate(gate.lockId);
|
|
13310
|
+
}
|
|
13311
|
+
});
|
|
13312
|
+
|
|
13313
|
+
// src/api/g2-routes.ts
|
|
13314
|
+
import { Hono as Hono12 } from "hono";
|
|
13315
|
+
import { z as z20 } from "zod";
|
|
13316
|
+
var G2ReviewsBodySchema = z20.object({
|
|
13317
|
+
product: z20.string().trim().min(1, "product is required"),
|
|
13318
|
+
maxPages: z20.number().int().min(1).max(50).default(5)
|
|
13319
|
+
});
|
|
13320
|
+
function invalidRequest7(message) {
|
|
13321
|
+
return { error_code: "invalid_request", message };
|
|
13322
|
+
}
|
|
13323
|
+
function normalizeProductSlug(input) {
|
|
13324
|
+
return input.trim().replace(/^https?:\/\/(www\.)?g2\.com\/products\//i, "").replace(/\/.*$/, "").replace(/\s+/g, "-").toLowerCase();
|
|
13325
|
+
}
|
|
13326
|
+
function reviewPageUrl2(product, page) {
|
|
13327
|
+
const base = `https://www.g2.com/products/${encodeURIComponent(product)}/reviews`;
|
|
13328
|
+
return page <= 1 ? base : `${base}?page=${page}`;
|
|
13329
|
+
}
|
|
13330
|
+
async function residentialProxyId2(attemptIndex) {
|
|
13331
|
+
try {
|
|
13332
|
+
const r = await resolveKernelProxyId({
|
|
13333
|
+
kernelApiKey: browserServiceApiKey(),
|
|
13334
|
+
proxyMode: "location",
|
|
13335
|
+
location: "Austin, TX",
|
|
13336
|
+
gl: "us",
|
|
13337
|
+
attemptIndex,
|
|
13338
|
+
fresh: true
|
|
13339
|
+
});
|
|
13340
|
+
return r.kernelProxyId;
|
|
13341
|
+
} catch {
|
|
13342
|
+
return void 0;
|
|
13343
|
+
}
|
|
13344
|
+
}
|
|
13345
|
+
async function extractG2ReviewCards(page) {
|
|
13346
|
+
return page.evaluate(() => {
|
|
13347
|
+
const cards = Array.from(document.querySelectorAll("article[data-track-in-viewport-options]"));
|
|
13348
|
+
return cards.map((card) => {
|
|
13349
|
+
let meta = {};
|
|
13350
|
+
try {
|
|
13351
|
+
meta = JSON.parse(card.getAttribute("data-track-in-viewport-options") ?? "{}");
|
|
13352
|
+
} catch {
|
|
13353
|
+
meta = {};
|
|
13354
|
+
}
|
|
13355
|
+
const reviewId = meta.survey_response_id != null ? String(meta.survey_response_id) : null;
|
|
13356
|
+
const publishedDateRaw = typeof meta.published_date === "string" ? meta.published_date : null;
|
|
13357
|
+
const date = publishedDateRaw && /^\d{8}$/.test(publishedDateRaw) ? `${publishedDateRaw.slice(0, 4)}-${publishedDateRaw.slice(4, 6)}-${publishedDateRaw.slice(6, 8)}` : null;
|
|
13358
|
+
const profileLink = card.querySelector('a[href*="/users/"]');
|
|
13359
|
+
const profileHref = profileLink?.getAttribute("href") ?? "";
|
|
13360
|
+
const profileMatch = profileHref.match(/\/users\/([a-f0-9-]+)/i);
|
|
13361
|
+
const profileId = profileMatch ? profileMatch[1] : null;
|
|
13362
|
+
const infoWrapper = profileLink?.parentElement;
|
|
13363
|
+
const infoDiv = infoWrapper ? Array.from(infoWrapper.children).find((el2) => el2 !== profileLink) : null;
|
|
13364
|
+
const name = infoDiv?.querySelector('div[class*="font-bold"]')?.textContent?.trim() ?? null;
|
|
13365
|
+
const subtleDivs = infoDiv ? Array.from(infoDiv.querySelectorAll('div[class*="text-subtle"]')) : [];
|
|
13366
|
+
const reviewerTitle = subtleDivs[0]?.textContent?.trim() ?? null;
|
|
13367
|
+
const companySegment = subtleDivs[1]?.textContent?.trim() ?? null;
|
|
13368
|
+
const ratingEl = card.querySelector('[class*="star-wrapper__desc__rating"]');
|
|
13369
|
+
const ratingText = ratingEl?.textContent?.trim() ?? "";
|
|
13370
|
+
const ratingParts = ratingText.split("/");
|
|
13371
|
+
const rating = ratingParts[0] ? parseFloat(ratingParts[0]) : null;
|
|
13372
|
+
const ratingScale = ratingParts[1] ? parseFloat(ratingParts[1]) : 5;
|
|
13373
|
+
const titleEl = card.querySelector('div[class*="text-lg"][class*="font-bold"]');
|
|
13374
|
+
const title = titleEl?.textContent?.trim().replace(/^["“]+|["”]+$/g, "") ?? null;
|
|
13375
|
+
const sections = Array.from(card.querySelectorAll("section"));
|
|
13376
|
+
const body = sections.map((sec) => {
|
|
13377
|
+
const question = sec.querySelector('div[class*="font-bold"]')?.textContent?.trim() ?? null;
|
|
13378
|
+
const paragraphs = Array.from(sec.querySelectorAll("p")).map((p) => p.textContent?.trim() ?? "");
|
|
13379
|
+
const answer = paragraphs.join("\n\n").replace(/Review collected by and hosted on G2\.com\.\s*$/, "").trim();
|
|
13380
|
+
return { question, answer };
|
|
13381
|
+
}).filter((b) => b.answer.length > 0);
|
|
13382
|
+
const flagButtons = Array.from(card.querySelectorAll("button[title]"));
|
|
13383
|
+
let currentUser = false;
|
|
13384
|
+
let validated = false;
|
|
13385
|
+
let incentivized = false;
|
|
13386
|
+
let origin = null;
|
|
13387
|
+
for (const btn of flagButtons) {
|
|
13388
|
+
const label = btn.querySelector("label")?.textContent?.trim() ?? "";
|
|
13389
|
+
if (/current user/i.test(label)) currentUser = true;
|
|
13390
|
+
else if (/validated/i.test(label)) validated = true;
|
|
13391
|
+
else if (/^incentivized$/i.test(label)) incentivized = true;
|
|
13392
|
+
else if (/^source:/i.test(label)) origin = label.replace(/^source:\s*/i, "").trim();
|
|
13393
|
+
}
|
|
13394
|
+
const copyBtn = card.querySelector('[data-clipboard-text*="/reviews/"]');
|
|
13395
|
+
const reviewUrl = copyBtn?.getAttribute("data-clipboard-text") ?? null;
|
|
13396
|
+
return {
|
|
13397
|
+
source: "g2",
|
|
13398
|
+
sourceReviewId: reviewId,
|
|
13399
|
+
reviewUrl,
|
|
13400
|
+
reviewer: {
|
|
13401
|
+
name,
|
|
13402
|
+
profileId,
|
|
13403
|
+
title: reviewerTitle,
|
|
13404
|
+
companySegment
|
|
13405
|
+
},
|
|
13406
|
+
rating,
|
|
13407
|
+
ratingScale,
|
|
13408
|
+
title,
|
|
13409
|
+
date,
|
|
13410
|
+
body: body.length > 0 ? body : [{ question: null, answer: "" }],
|
|
13411
|
+
truncated: false,
|
|
13412
|
+
flags: {
|
|
13413
|
+
origin,
|
|
13414
|
+
incentivized,
|
|
13415
|
+
validated,
|
|
13416
|
+
currentUser,
|
|
13417
|
+
companyReplied: null
|
|
13418
|
+
}
|
|
13419
|
+
};
|
|
13420
|
+
});
|
|
13421
|
+
});
|
|
13422
|
+
}
|
|
13423
|
+
var g2App = new Hono12();
|
|
13424
|
+
g2App.post("/reviews", createApiKeyAuth(), async (c) => {
|
|
13425
|
+
const raw = await c.req.json().catch(() => ({}));
|
|
13426
|
+
const parsed = G2ReviewsBodySchema.safeParse(raw);
|
|
13427
|
+
if (!parsed.success) return c.json(invalidRequest7(parsed.error.issues[0]?.message ?? "Invalid request"), 400);
|
|
13428
|
+
const body = parsed.data;
|
|
13429
|
+
const product = normalizeProductSlug(body.product);
|
|
13430
|
+
const user = c.get("user");
|
|
13431
|
+
const gate = await acquireConcurrencyGate(user, "g2_reviews", {
|
|
13432
|
+
reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"),
|
|
13433
|
+
metadata: { product, maxPages: body.maxPages }
|
|
13434
|
+
});
|
|
13435
|
+
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
13436
|
+
let debited = false;
|
|
13437
|
+
let reviewDebitMc = 0;
|
|
13438
|
+
try {
|
|
13439
|
+
const { ok, balance_mc } = await debitMc(user.id, MC_COSTS.g2_reviews, LedgerOperation.G2_REVIEWS, product);
|
|
13440
|
+
if (!ok) return c.json(insufficientBalanceResponse(balance_mc, MC_COSTS.g2_reviews), 402);
|
|
13441
|
+
debited = true;
|
|
13442
|
+
const startMs = Date.now();
|
|
13443
|
+
const allReviews = [];
|
|
13444
|
+
let pagesFetched = 0;
|
|
13445
|
+
let blocked = false;
|
|
13446
|
+
for (let attempt = 0; attempt < 3 && pagesFetched === 0; attempt++) {
|
|
13447
|
+
const proxyId = await residentialProxyId2(attempt);
|
|
13448
|
+
const driver = new BrowserDriver();
|
|
13449
|
+
try {
|
|
13450
|
+
await driver.launch({
|
|
13451
|
+
headless: true,
|
|
13452
|
+
kernelApiKey: browserServiceApiKey(),
|
|
13453
|
+
kernelProxyId: proxyId,
|
|
13454
|
+
viewport: { width: 1280, height: 900 },
|
|
13455
|
+
locale: "en-US"
|
|
13456
|
+
});
|
|
13457
|
+
const page = driver.getPage();
|
|
13458
|
+
for (let pageNum = 1; pageNum <= body.maxPages; pageNum++) {
|
|
13459
|
+
const url = reviewPageUrl2(product, pageNum);
|
|
13460
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 45e3 });
|
|
13461
|
+
await page.waitForTimeout(5e3);
|
|
13462
|
+
await page.evaluate(() => window.scrollTo(0, 1200)).catch(() => {
|
|
13463
|
+
});
|
|
13464
|
+
await page.waitForTimeout(3e3);
|
|
13465
|
+
let cards;
|
|
13466
|
+
try {
|
|
13467
|
+
cards = await extractG2ReviewCards(page);
|
|
13468
|
+
} catch {
|
|
13469
|
+
await page.waitForTimeout(3e3);
|
|
13470
|
+
cards = await extractG2ReviewCards(page);
|
|
13471
|
+
}
|
|
13472
|
+
if (cards.length === 0) break;
|
|
13473
|
+
allReviews.push(...cards);
|
|
13474
|
+
pagesFetched = pageNum;
|
|
13475
|
+
}
|
|
13476
|
+
} catch {
|
|
13477
|
+
blocked = true;
|
|
13478
|
+
} finally {
|
|
13479
|
+
await driver.close();
|
|
13480
|
+
}
|
|
13481
|
+
}
|
|
13482
|
+
if (pagesFetched === 0 || allReviews.length === 0) {
|
|
13483
|
+
await creditMc(user.id, MC_COSTS.g2_reviews, LedgerOperation.G2_REVIEWS_REFUND, blocked ? "blocked after retries" : "no reviews found");
|
|
13484
|
+
await logRequestEvent({ userId: user.id, source: "g2_reviews", status: "failed", query: product, error: blocked ? "blocked after retries" : "no reviews found" });
|
|
13485
|
+
return c.json({ error: "No reviews found for this product (refunded)", product }, 404);
|
|
13486
|
+
}
|
|
13487
|
+
reviewDebitMc = allReviews.length * MC_COSTS.g2_review;
|
|
13488
|
+
if (reviewDebitMc > 0) {
|
|
13489
|
+
const reviewDebit = await debitMc(user.id, reviewDebitMc, LedgerOperation.G2_REVIEW, product);
|
|
13490
|
+
if (!reviewDebit.ok) {
|
|
13491
|
+
allReviews.length = Math.floor((reviewDebit.balance_mc ?? 0) / MC_COSTS.g2_review);
|
|
13492
|
+
reviewDebitMc = allReviews.length * MC_COSTS.g2_review;
|
|
13493
|
+
if (reviewDebitMc > 0) await debitMc(user.id, reviewDebitMc, LedgerOperation.G2_REVIEW, product);
|
|
13494
|
+
}
|
|
13495
|
+
}
|
|
13496
|
+
const result = {
|
|
13497
|
+
product,
|
|
13498
|
+
reviewUrl: reviewPageUrl2(product, 1),
|
|
13499
|
+
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13500
|
+
requestedMaxPages: body.maxPages,
|
|
13501
|
+
pagesFetched,
|
|
13502
|
+
reviewCount: allReviews.length,
|
|
13503
|
+
reviews: allReviews,
|
|
13504
|
+
durationMs: Date.now() - startMs
|
|
13505
|
+
};
|
|
13506
|
+
await logRequestEvent({ userId: user.id, source: "g2_reviews", status: "done", query: product, resultCount: allReviews.length, result });
|
|
13507
|
+
return c.json(result);
|
|
13508
|
+
} catch (err) {
|
|
13509
|
+
if (debited) await creditMc(user.id, MC_COSTS.g2_reviews, LedgerOperation.G2_REVIEWS_REFUND, "failed call");
|
|
13510
|
+
if (reviewDebitMc > 0) await creditMc(user.id, reviewDebitMc, LedgerOperation.G2_REVIEW_REFUND, "failed call");
|
|
13511
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
13512
|
+
await logRequestEvent({ userId: user.id, source: "g2_reviews", status: "failed", query: product, error: msg });
|
|
13513
|
+
return c.json({ error: msg }, 500);
|
|
13514
|
+
} finally {
|
|
13515
|
+
await releaseConcurrencyGate(gate.lockId);
|
|
13516
|
+
}
|
|
13517
|
+
});
|
|
13518
|
+
|
|
13519
|
+
// src/api/directory-routes.ts
|
|
13520
|
+
import { Hono as Hono13 } from "hono";
|
|
13165
13521
|
|
|
13166
13522
|
// src/directory/directory-workflow.ts
|
|
13167
13523
|
import { mkdir as mkdir2, writeFile } from "fs/promises";
|
|
13168
13524
|
import { join as join5 } from "path";
|
|
13169
|
-
import { z as
|
|
13525
|
+
import { z as z21 } from "zod";
|
|
13170
13526
|
|
|
13171
13527
|
// src/directory/location-db.ts
|
|
13172
13528
|
import { access, readFile } from "fs/promises";
|
|
@@ -13327,24 +13683,24 @@ async function resolveDirectoryMarkets(options) {
|
|
|
13327
13683
|
}
|
|
13328
13684
|
|
|
13329
13685
|
// src/directory/directory-workflow.ts
|
|
13330
|
-
var DirectoryWorkflowOptionsSchema =
|
|
13331
|
-
query:
|
|
13332
|
-
state:
|
|
13333
|
-
minPopulation:
|
|
13334
|
-
populationYear:
|
|
13335
|
-
maxCities:
|
|
13336
|
-
maxResultsPerCity:
|
|
13337
|
-
concurrency:
|
|
13338
|
-
includeZipGroups:
|
|
13339
|
-
usZipsCsvPath:
|
|
13340
|
-
saveCsv:
|
|
13341
|
-
gl:
|
|
13342
|
-
hl:
|
|
13343
|
-
proxyMode:
|
|
13344
|
-
proxyZip:
|
|
13345
|
-
debug:
|
|
13346
|
-
headless:
|
|
13347
|
-
kernelApiKey:
|
|
13686
|
+
var DirectoryWorkflowOptionsSchema = z21.object({
|
|
13687
|
+
query: z21.string().min(1),
|
|
13688
|
+
state: z21.string().min(2).default("TN"),
|
|
13689
|
+
minPopulation: z21.number().int().min(0).default(1e5),
|
|
13690
|
+
populationYear: z21.union(POPULATION_YEARS.map((year) => z21.literal(year))).default(2025),
|
|
13691
|
+
maxCities: z21.number().int().min(1).max(100).default(25),
|
|
13692
|
+
maxResultsPerCity: z21.number().int().min(1).max(50).default(50),
|
|
13693
|
+
concurrency: z21.number().int().min(1).max(5).default(5),
|
|
13694
|
+
includeZipGroups: z21.boolean().default(true),
|
|
13695
|
+
usZipsCsvPath: z21.string().optional(),
|
|
13696
|
+
saveCsv: z21.boolean().default(true),
|
|
13697
|
+
gl: z21.string().length(2).default("us"),
|
|
13698
|
+
hl: z21.string().length(2).default("en"),
|
|
13699
|
+
proxyMode: z21.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
|
|
13700
|
+
proxyZip: z21.string().regex(/^\d{5}$/).optional(),
|
|
13701
|
+
debug: z21.boolean().default(false),
|
|
13702
|
+
headless: z21.boolean().default(true),
|
|
13703
|
+
kernelApiKey: z21.string().optional()
|
|
13348
13704
|
});
|
|
13349
13705
|
function errorMessage(err) {
|
|
13350
13706
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -13565,7 +13921,7 @@ async function runDirectoryWorkflowFromPlan(options, plan) {
|
|
|
13565
13921
|
}
|
|
13566
13922
|
|
|
13567
13923
|
// src/api/directory-routes.ts
|
|
13568
|
-
var directoryApp = new
|
|
13924
|
+
var directoryApp = new Hono13();
|
|
13569
13925
|
directoryApp.post("/run", createApiKeyAuth(), async (c) => {
|
|
13570
13926
|
const user = c.get("user");
|
|
13571
13927
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -13638,35 +13994,35 @@ directoryApp.post("/run", createApiKeyAuth(), async (c) => {
|
|
|
13638
13994
|
// src/api/workflow-routes.ts
|
|
13639
13995
|
import { createHmac as createHmac2 } from "crypto";
|
|
13640
13996
|
import { readFile as readFile2 } from "fs/promises";
|
|
13641
|
-
import { Hono as
|
|
13642
|
-
import { z as
|
|
13643
|
-
var workflowApp = new
|
|
13644
|
-
var WorkflowInputSchema =
|
|
13645
|
-
var WorkflowIdSchema =
|
|
13646
|
-
var CadenceSchema =
|
|
13647
|
-
var ScheduleStatusSchema =
|
|
13648
|
-
var RunBodySchema =
|
|
13997
|
+
import { Hono as Hono14 } from "hono";
|
|
13998
|
+
import { z as z22 } from "zod";
|
|
13999
|
+
var workflowApp = new Hono14();
|
|
14000
|
+
var WorkflowInputSchema = z22.record(z22.unknown()).default({});
|
|
14001
|
+
var WorkflowIdSchema = z22.string().min(1);
|
|
14002
|
+
var CadenceSchema = z22.enum(["daily", "weekly", "monthly"]);
|
|
14003
|
+
var ScheduleStatusSchema = z22.enum(["active", "paused"]);
|
|
14004
|
+
var RunBodySchema = z22.object({
|
|
13649
14005
|
workflowId: WorkflowIdSchema,
|
|
13650
14006
|
input: WorkflowInputSchema,
|
|
13651
|
-
webhookUrl:
|
|
14007
|
+
webhookUrl: z22.string().url().optional()
|
|
13652
14008
|
});
|
|
13653
|
-
var ScheduleCreateSchema =
|
|
14009
|
+
var ScheduleCreateSchema = z22.object({
|
|
13654
14010
|
workflowId: WorkflowIdSchema,
|
|
13655
|
-
name:
|
|
14011
|
+
name: z22.string().min(1).max(120).optional(),
|
|
13656
14012
|
input: WorkflowInputSchema,
|
|
13657
14013
|
cadence: CadenceSchema.default("weekly"),
|
|
13658
|
-
timezone:
|
|
13659
|
-
webhookUrl:
|
|
13660
|
-
nextRunAt:
|
|
14014
|
+
timezone: z22.string().min(1).max(64).default("UTC"),
|
|
14015
|
+
webhookUrl: z22.string().url().optional(),
|
|
14016
|
+
nextRunAt: z22.string().datetime().optional()
|
|
13661
14017
|
});
|
|
13662
|
-
var SchedulePatchSchema =
|
|
13663
|
-
name:
|
|
14018
|
+
var SchedulePatchSchema = z22.object({
|
|
14019
|
+
name: z22.string().min(1).max(120).optional(),
|
|
13664
14020
|
status: ScheduleStatusSchema.optional(),
|
|
13665
14021
|
input: WorkflowInputSchema.optional(),
|
|
13666
14022
|
cadence: CadenceSchema.optional(),
|
|
13667
|
-
timezone:
|
|
13668
|
-
webhookUrl:
|
|
13669
|
-
nextRunAt:
|
|
14023
|
+
timezone: z22.string().min(1).max(64).optional(),
|
|
14024
|
+
webhookUrl: z22.string().url().nullable().optional(),
|
|
14025
|
+
nextRunAt: z22.string().datetime().nullable().optional()
|
|
13670
14026
|
});
|
|
13671
14027
|
function hostedWorkflowOutputDir() {
|
|
13672
14028
|
return workflowOutputBaseDir(process.env.MCP_SCRAPER_WORKFLOW_OUTPUT_DIR?.trim() || "/tmp/mcp-scraper-workflows");
|
|
@@ -14170,7 +14526,7 @@ workflowApp.post("/cron/dispatch", async (c) => {
|
|
|
14170
14526
|
});
|
|
14171
14527
|
|
|
14172
14528
|
// src/api/serp-intelligence-routes.ts
|
|
14173
|
-
import { Hono as
|
|
14529
|
+
import { Hono as Hono15 } from "hono";
|
|
14174
14530
|
|
|
14175
14531
|
// src/serp-intelligence/page-snapshot-extractor.ts
|
|
14176
14532
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -14482,7 +14838,7 @@ async function capturePageSnapshots(targets, options = {}) {
|
|
|
14482
14838
|
}
|
|
14483
14839
|
|
|
14484
14840
|
// src/serp-intelligence/schemas.ts
|
|
14485
|
-
import { z as
|
|
14841
|
+
import { z as z23 } from "zod";
|
|
14486
14842
|
var SerpIntelligenceDeviceValues = ["desktop", "mobile"];
|
|
14487
14843
|
var SerpIntelligenceProxyModeValues = ["location", "configured", "none"];
|
|
14488
14844
|
var SerpIntelligenceAttemptOutcomeValues = [
|
|
@@ -14544,171 +14900,171 @@ function isPublicHttpUrl(value) {
|
|
|
14544
14900
|
return false;
|
|
14545
14901
|
}
|
|
14546
14902
|
}
|
|
14547
|
-
var SerpIntelligencePublicHttpUrlSchema =
|
|
14548
|
-
var SerpIntelligenceCaptureBodySchema =
|
|
14549
|
-
query:
|
|
14550
|
-
location:
|
|
14551
|
-
gl:
|
|
14552
|
-
hl:
|
|
14553
|
-
device:
|
|
14554
|
-
proxyMode:
|
|
14555
|
-
proxyZip:
|
|
14556
|
-
pages:
|
|
14557
|
-
debug:
|
|
14558
|
-
includePageSnapshots:
|
|
14559
|
-
pageSnapshotLimit:
|
|
14903
|
+
var SerpIntelligencePublicHttpUrlSchema = z23.string().url().refine(isPublicHttpUrl, "url must be a public HTTP or HTTPS URL");
|
|
14904
|
+
var SerpIntelligenceCaptureBodySchema = z23.object({
|
|
14905
|
+
query: z23.string().trim().min(1, "query is required"),
|
|
14906
|
+
location: z23.string().trim().min(1).optional(),
|
|
14907
|
+
gl: z23.string().trim().length(2).default("us"),
|
|
14908
|
+
hl: z23.string().trim().length(2).default("en"),
|
|
14909
|
+
device: z23.enum(SerpIntelligenceDeviceValues).default("desktop"),
|
|
14910
|
+
proxyMode: z23.enum(SerpIntelligenceProxyModeValues).default(DEFAULT_PROXY_MODE),
|
|
14911
|
+
proxyZip: z23.string().regex(/^\d{5}$/).optional(),
|
|
14912
|
+
pages: z23.number().int().min(1).max(2).default(1),
|
|
14913
|
+
debug: z23.boolean().default(false),
|
|
14914
|
+
includePageSnapshots: z23.boolean().default(false),
|
|
14915
|
+
pageSnapshotLimit: z23.number().int().min(0).max(10).default(0)
|
|
14560
14916
|
}).strict();
|
|
14561
|
-
var SerpIntelligencePageSnapshotRequestSchema =
|
|
14917
|
+
var SerpIntelligencePageSnapshotRequestSchema = z23.object({
|
|
14562
14918
|
url: SerpIntelligencePublicHttpUrlSchema,
|
|
14563
|
-
sourceKind:
|
|
14564
|
-
sourcePosition:
|
|
14919
|
+
sourceKind: z23.enum(SerpPageSnapshotSourceKindValues).default("configured_target"),
|
|
14920
|
+
sourcePosition: z23.number().int().min(1).optional()
|
|
14565
14921
|
}).strict();
|
|
14566
|
-
var SerpIntelligencePageSnapshotsBodySchema =
|
|
14567
|
-
urls:
|
|
14568
|
-
targets:
|
|
14569
|
-
maxConcurrency:
|
|
14570
|
-
timeoutMs:
|
|
14571
|
-
debug:
|
|
14922
|
+
var SerpIntelligencePageSnapshotsBodySchema = z23.object({
|
|
14923
|
+
urls: z23.array(SerpIntelligencePublicHttpUrlSchema).min(1).max(25),
|
|
14924
|
+
targets: z23.array(SerpIntelligencePageSnapshotRequestSchema).min(1).max(25).optional(),
|
|
14925
|
+
maxConcurrency: z23.number().int().min(1).max(5).default(2),
|
|
14926
|
+
timeoutMs: z23.number().int().min(1e3).max(6e4).default(15e3),
|
|
14927
|
+
debug: z23.boolean().default(false)
|
|
14572
14928
|
}).strict();
|
|
14573
|
-
var SerpIntelligenceAICitationSchema =
|
|
14574
|
-
text:
|
|
14575
|
-
href:
|
|
14929
|
+
var SerpIntelligenceAICitationSchema = z23.object({
|
|
14930
|
+
text: z23.string(),
|
|
14931
|
+
href: z23.string()
|
|
14576
14932
|
}).strict();
|
|
14577
|
-
var SerpIntelligenceOrganicResultSchema =
|
|
14578
|
-
position:
|
|
14579
|
-
title:
|
|
14580
|
-
url:
|
|
14581
|
-
domain:
|
|
14582
|
-
cite:
|
|
14583
|
-
snippet:
|
|
14584
|
-
isRedditStyle:
|
|
14585
|
-
inlineRating:
|
|
14586
|
-
value:
|
|
14587
|
-
count:
|
|
14933
|
+
var SerpIntelligenceOrganicResultSchema = z23.object({
|
|
14934
|
+
position: z23.number().int().min(1),
|
|
14935
|
+
title: z23.string(),
|
|
14936
|
+
url: z23.string(),
|
|
14937
|
+
domain: z23.string(),
|
|
14938
|
+
cite: z23.string().nullable(),
|
|
14939
|
+
snippet: z23.string().nullable(),
|
|
14940
|
+
isRedditStyle: z23.boolean(),
|
|
14941
|
+
inlineRating: z23.object({
|
|
14942
|
+
value: z23.string(),
|
|
14943
|
+
count: z23.string()
|
|
14588
14944
|
}).strict().nullable()
|
|
14589
14945
|
}).strict();
|
|
14590
|
-
var SerpIntelligenceLocationEvidenceSchema =
|
|
14591
|
-
status:
|
|
14592
|
-
expected:
|
|
14593
|
-
city:
|
|
14594
|
-
regionCode:
|
|
14595
|
-
canonicalLocation:
|
|
14946
|
+
var SerpIntelligenceLocationEvidenceSchema = z23.object({
|
|
14947
|
+
status: z23.enum(SerpIntelligenceLocalizationStatusValues),
|
|
14948
|
+
expected: z23.object({
|
|
14949
|
+
city: z23.string(),
|
|
14950
|
+
regionCode: z23.string().nullable(),
|
|
14951
|
+
canonicalLocation: z23.string()
|
|
14596
14952
|
}).strict().nullable(),
|
|
14597
|
-
candidates:
|
|
14598
|
-
city:
|
|
14599
|
-
regionCode:
|
|
14600
|
-
count:
|
|
14601
|
-
examples:
|
|
14953
|
+
candidates: z23.array(z23.object({
|
|
14954
|
+
city: z23.string(),
|
|
14955
|
+
regionCode: z23.string(),
|
|
14956
|
+
count: z23.number().int().min(0),
|
|
14957
|
+
examples: z23.array(z23.string())
|
|
14602
14958
|
}).strict())
|
|
14603
14959
|
}).strict();
|
|
14604
|
-
var SerpIntelligenceHarvestResultSchema =
|
|
14605
|
-
seed:
|
|
14606
|
-
location:
|
|
14607
|
-
extractedAt:
|
|
14608
|
-
totalQuestions:
|
|
14609
|
-
surface:
|
|
14610
|
-
aiOverview:
|
|
14611
|
-
detected:
|
|
14612
|
-
text:
|
|
14613
|
-
citations:
|
|
14614
|
-
expanded:
|
|
14615
|
-
fullyExpanded:
|
|
14616
|
-
sections:
|
|
14960
|
+
var SerpIntelligenceHarvestResultSchema = z23.object({
|
|
14961
|
+
seed: z23.string(),
|
|
14962
|
+
location: z23.string().nullable(),
|
|
14963
|
+
extractedAt: z23.string(),
|
|
14964
|
+
totalQuestions: z23.number().int().min(0),
|
|
14965
|
+
surface: z23.enum(["web", "aim", "unknown"]),
|
|
14966
|
+
aiOverview: z23.object({
|
|
14967
|
+
detected: z23.boolean(),
|
|
14968
|
+
text: z23.string().nullable(),
|
|
14969
|
+
citations: z23.array(SerpIntelligenceAICitationSchema),
|
|
14970
|
+
expanded: z23.boolean().optional(),
|
|
14971
|
+
fullyExpanded: z23.boolean().optional(),
|
|
14972
|
+
sections: z23.array(z23.string()).optional()
|
|
14617
14973
|
}).strict(),
|
|
14618
|
-
aiMode:
|
|
14619
|
-
detected:
|
|
14620
|
-
text:
|
|
14621
|
-
citations:
|
|
14974
|
+
aiMode: z23.object({
|
|
14975
|
+
detected: z23.boolean(),
|
|
14976
|
+
text: z23.string().nullable(),
|
|
14977
|
+
citations: z23.array(SerpIntelligenceAICitationSchema)
|
|
14622
14978
|
}).strict(),
|
|
14623
|
-
tree:
|
|
14624
|
-
flat:
|
|
14625
|
-
videos:
|
|
14626
|
-
forums:
|
|
14627
|
-
organicResults:
|
|
14628
|
-
localPack:
|
|
14629
|
-
entityIds:
|
|
14630
|
-
entities:
|
|
14631
|
-
name:
|
|
14632
|
-
kgId:
|
|
14633
|
-
cid:
|
|
14634
|
-
gcid:
|
|
14979
|
+
tree: z23.array(z23.unknown()),
|
|
14980
|
+
flat: z23.array(z23.unknown()),
|
|
14981
|
+
videos: z23.array(z23.unknown()),
|
|
14982
|
+
forums: z23.array(z23.unknown()),
|
|
14983
|
+
organicResults: z23.array(SerpIntelligenceOrganicResultSchema),
|
|
14984
|
+
localPack: z23.array(z23.unknown()),
|
|
14985
|
+
entityIds: z23.object({
|
|
14986
|
+
entities: z23.array(z23.object({
|
|
14987
|
+
name: z23.string(),
|
|
14988
|
+
kgId: z23.string().nullable(),
|
|
14989
|
+
cid: z23.string().nullable(),
|
|
14990
|
+
gcid: z23.string().nullable()
|
|
14635
14991
|
}).strict()),
|
|
14636
|
-
kgIds:
|
|
14637
|
-
cids:
|
|
14638
|
-
gcids:
|
|
14992
|
+
kgIds: z23.array(z23.string()),
|
|
14993
|
+
cids: z23.array(z23.string()),
|
|
14994
|
+
gcids: z23.array(z23.string())
|
|
14639
14995
|
}).strict(),
|
|
14640
|
-
stats:
|
|
14641
|
-
seed:
|
|
14642
|
-
totalQuestions:
|
|
14643
|
-
maxDepthReached:
|
|
14644
|
-
durationMs:
|
|
14645
|
-
errorCount:
|
|
14996
|
+
stats: z23.object({
|
|
14997
|
+
seed: z23.string(),
|
|
14998
|
+
totalQuestions: z23.number().int().min(0),
|
|
14999
|
+
maxDepthReached: z23.number().int().min(0),
|
|
15000
|
+
durationMs: z23.number().min(0),
|
|
15001
|
+
errorCount: z23.number().int().min(0)
|
|
14646
15002
|
}).strict(),
|
|
14647
|
-
diagnostics:
|
|
14648
|
-
completionStatus:
|
|
14649
|
-
problem:
|
|
14650
|
-
warnings:
|
|
14651
|
-
debug:
|
|
15003
|
+
diagnostics: z23.object({
|
|
15004
|
+
completionStatus: z23.enum(["paa_found", "no_paa", "serp_only"]),
|
|
15005
|
+
problem: z23.null(),
|
|
15006
|
+
warnings: z23.array(z23.unknown()).optional(),
|
|
15007
|
+
debug: z23.object({
|
|
14652
15008
|
locationEvidence: SerpIntelligenceLocationEvidenceSchema.optional()
|
|
14653
15009
|
}).passthrough().optional()
|
|
14654
15010
|
}).passthrough(),
|
|
14655
|
-
whatPeopleSaying:
|
|
15011
|
+
whatPeopleSaying: z23.array(z23.unknown())
|
|
14656
15012
|
}).strict();
|
|
14657
|
-
var SerpIntelligenceCaptureAttemptSchema =
|
|
14658
|
-
attemptNumber:
|
|
14659
|
-
outcome:
|
|
14660
|
-
startedAt:
|
|
14661
|
-
completedAt:
|
|
14662
|
-
durationMs:
|
|
14663
|
-
problemCode:
|
|
14664
|
-
message:
|
|
14665
|
-
kernelSessionId:
|
|
14666
|
-
cleanupSucceeded:
|
|
15013
|
+
var SerpIntelligenceCaptureAttemptSchema = z23.object({
|
|
15014
|
+
attemptNumber: z23.number().int().min(1),
|
|
15015
|
+
outcome: z23.enum(SerpIntelligenceAttemptOutcomeValues),
|
|
15016
|
+
startedAt: z23.string().optional(),
|
|
15017
|
+
completedAt: z23.string().optional(),
|
|
15018
|
+
durationMs: z23.number().min(0).optional(),
|
|
15019
|
+
problemCode: z23.string().optional(),
|
|
15020
|
+
message: z23.string().optional(),
|
|
15021
|
+
kernelSessionId: z23.string().nullable().optional(),
|
|
15022
|
+
cleanupSucceeded: z23.boolean().nullable().optional()
|
|
14667
15023
|
}).strict();
|
|
14668
|
-
var SerpPageSnapshotCaptureSchema =
|
|
15024
|
+
var SerpPageSnapshotCaptureSchema = z23.object({
|
|
14669
15025
|
url: SerpIntelligencePublicHttpUrlSchema,
|
|
14670
15026
|
requestedUrl: SerpIntelligencePublicHttpUrlSchema,
|
|
14671
15027
|
finalUrl: SerpIntelligencePublicHttpUrlSchema.nullable(),
|
|
14672
|
-
sourceKind:
|
|
14673
|
-
sourcePosition:
|
|
14674
|
-
status:
|
|
14675
|
-
fetchedVia:
|
|
14676
|
-
httpStatus:
|
|
14677
|
-
contentType:
|
|
14678
|
-
title:
|
|
15028
|
+
sourceKind: z23.enum(SerpPageSnapshotSourceKindValues),
|
|
15029
|
+
sourcePosition: z23.number().int().min(1).nullable(),
|
|
15030
|
+
status: z23.enum(SerpPageFetchStatusValues),
|
|
15031
|
+
fetchedVia: z23.enum(SerpPageFetchedViaValues).nullable(),
|
|
15032
|
+
httpStatus: z23.number().int().min(100).max(599).nullable(),
|
|
15033
|
+
contentType: z23.string().nullable(),
|
|
15034
|
+
title: z23.string().nullable(),
|
|
14679
15035
|
canonicalUrl: SerpIntelligencePublicHttpUrlSchema.nullable(),
|
|
14680
|
-
metaDescription:
|
|
14681
|
-
headings:
|
|
14682
|
-
level:
|
|
14683
|
-
text:
|
|
15036
|
+
metaDescription: z23.string().nullable(),
|
|
15037
|
+
headings: z23.array(z23.object({
|
|
15038
|
+
level: z23.number().int().min(1).max(6),
|
|
15039
|
+
text: z23.string()
|
|
14684
15040
|
}).strict()).default([]),
|
|
14685
|
-
artifact:
|
|
14686
|
-
htmlBlobUrl:
|
|
14687
|
-
textBlobUrl:
|
|
14688
|
-
markdownBlobUrl:
|
|
14689
|
-
screenshotBlobUrl:
|
|
14690
|
-
contentSha256:
|
|
14691
|
-
capturedAt:
|
|
15041
|
+
artifact: z23.object({
|
|
15042
|
+
htmlBlobUrl: z23.string().url().nullable(),
|
|
15043
|
+
textBlobUrl: z23.string().url().nullable(),
|
|
15044
|
+
markdownBlobUrl: z23.string().url().nullable(),
|
|
15045
|
+
screenshotBlobUrl: z23.string().url().nullable(),
|
|
15046
|
+
contentSha256: z23.string().nullable(),
|
|
15047
|
+
capturedAt: z23.string().nullable()
|
|
14692
15048
|
}).strict(),
|
|
14693
|
-
error:
|
|
14694
|
-
code:
|
|
14695
|
-
message:
|
|
15049
|
+
error: z23.object({
|
|
15050
|
+
code: z23.string(),
|
|
15051
|
+
message: z23.string()
|
|
14696
15052
|
}).strict().nullable()
|
|
14697
15053
|
}).strict();
|
|
14698
|
-
var SerpIntelligenceCaptureResponseSchema =
|
|
15054
|
+
var SerpIntelligenceCaptureResponseSchema = z23.object({
|
|
14699
15055
|
harvestResult: SerpIntelligenceHarvestResultSchema,
|
|
14700
|
-
attempts:
|
|
15056
|
+
attempts: z23.array(SerpIntelligenceCaptureAttemptSchema),
|
|
14701
15057
|
locationEvidence: SerpIntelligenceLocationEvidenceSchema.nullable(),
|
|
14702
|
-
pageSnapshotArtifacts:
|
|
14703
|
-
billing:
|
|
14704
|
-
creditsUsed:
|
|
14705
|
-
requestId:
|
|
14706
|
-
jobId:
|
|
15058
|
+
pageSnapshotArtifacts: z23.array(SerpPageSnapshotCaptureSchema),
|
|
15059
|
+
billing: z23.object({
|
|
15060
|
+
creditsUsed: z23.number().min(0).optional(),
|
|
15061
|
+
requestId: z23.string().optional(),
|
|
15062
|
+
jobId: z23.string().optional()
|
|
14707
15063
|
}).strict().optional()
|
|
14708
15064
|
}).strict();
|
|
14709
|
-
var SerpIntelligencePageSnapshotsResponseSchema =
|
|
14710
|
-
pageSnapshotArtifacts:
|
|
14711
|
-
attempts:
|
|
15065
|
+
var SerpIntelligencePageSnapshotsResponseSchema = z23.object({
|
|
15066
|
+
pageSnapshotArtifacts: z23.array(SerpPageSnapshotCaptureSchema),
|
|
15067
|
+
attempts: z23.array(SerpIntelligenceCaptureAttemptSchema).default([])
|
|
14712
15068
|
}).strict();
|
|
14713
15069
|
|
|
14714
15070
|
// src/serp-intelligence/serp-capture-service.ts
|
|
@@ -14880,7 +15236,7 @@ var SERP_INTELLIGENCE_RATE_LIMIT = 60;
|
|
|
14880
15236
|
var SERP_INTELLIGENCE_RATE_WINDOW_SECONDS = 60;
|
|
14881
15237
|
var POST_CAPTURE_ROUTE_LABEL = "POST /capture";
|
|
14882
15238
|
var POST_PAGE_SNAPSHOTS_ROUTE_LABEL = "POST /page-snapshots";
|
|
14883
|
-
var serpIntelligenceApp = new
|
|
15239
|
+
var serpIntelligenceApp = new Hono15();
|
|
14884
15240
|
serpIntelligenceApp.use("*", createApiKeyAuth());
|
|
14885
15241
|
function structuredError(input) {
|
|
14886
15242
|
return {
|
|
@@ -15049,7 +15405,7 @@ serpIntelligenceApp.post("/page-snapshots", async (c) => {
|
|
|
15049
15405
|
});
|
|
15050
15406
|
|
|
15051
15407
|
// src/mcp/mcp-routes.ts
|
|
15052
|
-
import { Hono as
|
|
15408
|
+
import { Hono as Hono16 } from "hono";
|
|
15053
15409
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
15054
15410
|
|
|
15055
15411
|
// src/mcp/memory-inprocess-executor.ts
|
|
@@ -15576,7 +15932,7 @@ async function requireMcpCallerKey(c) {
|
|
|
15576
15932
|
if (!user) return mcpAuthError();
|
|
15577
15933
|
return { key: callerKey, user };
|
|
15578
15934
|
}
|
|
15579
|
-
var mcpApp = new
|
|
15935
|
+
var mcpApp = new Hono16();
|
|
15580
15936
|
mcpApp.all("/", async (c) => {
|
|
15581
15937
|
try {
|
|
15582
15938
|
const keyOrError = await requireMcpCallerKey(c);
|
|
@@ -15602,7 +15958,7 @@ mcpApp.all("/", async (c) => {
|
|
|
15602
15958
|
});
|
|
15603
15959
|
|
|
15604
15960
|
// src/api/browser-agent-routes.ts
|
|
15605
|
-
import { Hono as
|
|
15961
|
+
import { Hono as Hono17 } from "hono";
|
|
15606
15962
|
|
|
15607
15963
|
// src/api/browser-agent-db.ts
|
|
15608
15964
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -16993,7 +17349,7 @@ async function loadOpenSession(id, userId) {
|
|
|
16993
17349
|
return row;
|
|
16994
17350
|
}
|
|
16995
17351
|
function buildBrowserAgentRoutes() {
|
|
16996
|
-
const app2 = new
|
|
17352
|
+
const app2 = new Hono17();
|
|
16997
17353
|
app2.use("*", async (c, next) => {
|
|
16998
17354
|
await migrateBrowserAgent();
|
|
16999
17355
|
return next();
|
|
@@ -17727,9 +18083,9 @@ if (state.key) { refreshSessions(); if (state.current) selectSession(state.curre
|
|
|
17727
18083
|
|
|
17728
18084
|
// src/api/stripe-routes.ts
|
|
17729
18085
|
import Stripe from "stripe";
|
|
17730
|
-
import { Hono as
|
|
18086
|
+
import { Hono as Hono18 } from "hono";
|
|
17731
18087
|
var stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: "2026-02-25.clover" });
|
|
17732
|
-
var stripeApp = new
|
|
18088
|
+
var stripeApp = new Hono18();
|
|
17733
18089
|
stripeApp.post("/webhooks", async (c) => {
|
|
17734
18090
|
const sig = c.req.header("stripe-signature");
|
|
17735
18091
|
const body = await c.req.text();
|
|
@@ -17833,7 +18189,7 @@ async function resolveUser(customerId, emailFallback) {
|
|
|
17833
18189
|
}
|
|
17834
18190
|
|
|
17835
18191
|
// src/api/oauth-routes.ts
|
|
17836
|
-
import { Hono as
|
|
18192
|
+
import { Hono as Hono19 } from "hono";
|
|
17837
18193
|
import { getCookie, setCookie } from "hono/cookie";
|
|
17838
18194
|
import { createHash as createHash3, randomBytes as randomBytes2, randomUUID as randomUUID3 } from "crypto";
|
|
17839
18195
|
import { importPKCS8, exportJWK, SignJWT } from "jose";
|
|
@@ -18002,7 +18358,7 @@ function redirectWithError(redirectUri, state, error) {
|
|
|
18002
18358
|
if (state) u.searchParams.set("state", state);
|
|
18003
18359
|
return new Response(null, { status: 302, headers: { Location: u.toString() } });
|
|
18004
18360
|
}
|
|
18005
|
-
var oauthApp = new
|
|
18361
|
+
var oauthApp = new Hono19();
|
|
18006
18362
|
oauthApp.use("*", async (c, next) => {
|
|
18007
18363
|
c.header("Access-Control-Allow-Origin", "*");
|
|
18008
18364
|
c.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
@@ -18256,8 +18612,8 @@ oauthApp.post("/oauth/token", async (c) => {
|
|
|
18256
18612
|
});
|
|
18257
18613
|
|
|
18258
18614
|
// src/api/chat-routes.ts
|
|
18259
|
-
import { Hono as
|
|
18260
|
-
var chatApp = new
|
|
18615
|
+
import { Hono as Hono20 } from "hono";
|
|
18616
|
+
var chatApp = new Hono20();
|
|
18261
18617
|
function tokenOk(token) {
|
|
18262
18618
|
return !!token && token.startsWith("mk_") && token.length > 10;
|
|
18263
18619
|
}
|
|
@@ -18927,8 +19283,8 @@ loadChannels();
|
|
|
18927
19283
|
}
|
|
18928
19284
|
|
|
18929
19285
|
// src/api/schedule-routes.ts
|
|
18930
|
-
import { Hono as
|
|
18931
|
-
var scheduleApp = new
|
|
19286
|
+
import { Hono as Hono21 } from "hono";
|
|
19287
|
+
var scheduleApp = new Hono21();
|
|
18932
19288
|
function tokenOk2(token) {
|
|
18933
19289
|
return !!token && token.startsWith("mk_") && token.length > 10;
|
|
18934
19290
|
}
|
|
@@ -19683,7 +20039,7 @@ var sessionAuth = createMiddleware3(async (c, next) => {
|
|
|
19683
20039
|
c.set("sessionUser", { ...refreshed, balance_mc: balanceMc });
|
|
19684
20040
|
return next();
|
|
19685
20041
|
});
|
|
19686
|
-
var app = new
|
|
20042
|
+
var app = new Hono22();
|
|
19687
20043
|
var STRIPE_API_VERSION = "2026-02-25.clover";
|
|
19688
20044
|
function requireStripeSecret() {
|
|
19689
20045
|
const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
|
|
@@ -21044,7 +21400,7 @@ app.get("/cron/tick", async (c) => {
|
|
|
21044
21400
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
21045
21401
|
return c.json({ error: "Unauthorized" }, 401);
|
|
21046
21402
|
}
|
|
21047
|
-
const { drainQueue } = await import("./worker-
|
|
21403
|
+
const { drainQueue } = await import("./worker-YHKUJR5P.js");
|
|
21048
21404
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
21049
21405
|
const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
|
|
21050
21406
|
const [results, sweepResult, reapResult, expiredResult, blobCleanup] = await Promise.all([
|
|
@@ -21062,7 +21418,7 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
|
|
|
21062
21418
|
return c.json({ error: "Unauthorized" }, 401);
|
|
21063
21419
|
}
|
|
21064
21420
|
const jobId = c.req.param("id");
|
|
21065
|
-
const { getExtractJob: getExtractJob2, completeExtractJob: completeExtractJob2 } = await import("./site-extract-repository-
|
|
21421
|
+
const { getExtractJob: getExtractJob2, completeExtractJob: completeExtractJob2 } = await import("./site-extract-repository-THBVEXMP.js");
|
|
21066
21422
|
const { assembleExtractArtifacts } = await import("./extract-bundle-UKE273TV.js");
|
|
21067
21423
|
const job = await getExtractJob2(jobId);
|
|
21068
21424
|
if (!job) return c.json({ error: "job not found" }, 404);
|
|
@@ -21070,7 +21426,7 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
|
|
|
21070
21426
|
await completeExtractJob2(jobId, stored);
|
|
21071
21427
|
let settlement = "already_settled_or_refunded";
|
|
21072
21428
|
if (job.billedMc == null && job.userId != null) {
|
|
21073
|
-
const { settleExtractJob: settleExtractJob2, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-
|
|
21429
|
+
const { settleExtractJob: settleExtractJob2, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-THBVEXMP.js");
|
|
21074
21430
|
const heldMc = Number(job.options.heldMc ?? 0);
|
|
21075
21431
|
const successful = await countSuccessfulPages2(jobId);
|
|
21076
21432
|
const usedMc = Math.min(successful * MC_COSTS.page_scrape, heldMc);
|
|
@@ -21096,6 +21452,8 @@ app.route("/instagram", instagramApp);
|
|
|
21096
21452
|
app.route("/reddit", redditApp);
|
|
21097
21453
|
app.route("/video", videoApp);
|
|
21098
21454
|
app.route("/maps", mapsApp);
|
|
21455
|
+
app.route("/trustpilot", trustpilotApp);
|
|
21456
|
+
app.route("/g2", g2App);
|
|
21099
21457
|
app.route("/directory", directoryApp);
|
|
21100
21458
|
app.route("/workflows", workflowApp);
|
|
21101
21459
|
app.route("/serp-intelligence", serpIntelligenceApp);
|
|
@@ -21232,4 +21590,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
21232
21590
|
export {
|
|
21233
21591
|
app
|
|
21234
21592
|
};
|
|
21235
|
-
//# sourceMappingURL=server-
|
|
21593
|
+
//# sourceMappingURL=server-IKT3QVFB.js.map
|