mcp-scraper 0.44.3 → 0.45.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 +1 -1
- package/dist/bin/api-server.cjs +4129 -3715
- 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 +2619 -2507
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +3 -3
- package/dist/chunk-CEZ3NFBP.js +7 -0
- package/dist/chunk-CEZ3NFBP.js.map +1 -0
- package/dist/{chunk-2SE4GYCS.js → chunk-FQIICRR4.js} +2849 -2741
- package/dist/chunk-FQIICRR4.js.map +1 -0
- package/dist/{chunk-WWIJ2NID.js → chunk-JFOT5JEL.js} +2 -2
- package/dist/{chunk-X5WDJ7D7.js → chunk-SOEYDWJU.js} +46 -3
- package/dist/chunk-SOEYDWJU.js.map +1 -0
- package/dist/{extract-bundle-U3MNYDKC.js → extract-bundle-DYMQRQWG.js} +3 -3
- package/dist/{server-JJLFY46K.js → server-7B75SDNG.js} +388 -135
- package/dist/server-7B75SDNG.js.map +1 -0
- package/dist/{site-extract-repository-CKCJDP7N.js → site-extract-repository-G723PZIL.js} +3 -3
- package/dist/{worker-73AYU4IK.js → worker-4LZGRHDA.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-2SE4GYCS.js.map +0 -1
- package/dist/chunk-32Z52OE4.js +0 -7
- package/dist/chunk-32Z52OE4.js.map +0 -1
- package/dist/chunk-X5WDJ7D7.js.map +0 -1
- package/dist/server-JJLFY46K.js.map +0 -1
- /package/dist/{chunk-WWIJ2NID.js.map → chunk-JFOT5JEL.js.map} +0 -0
- /package/dist/{extract-bundle-U3MNYDKC.js.map → extract-bundle-DYMQRQWG.js.map} +0 -0
- /package/dist/{site-extract-repository-CKCJDP7N.js.map → site-extract-repository-G723PZIL.js.map} +0 -0
- /package/dist/{worker-73AYU4IK.js.map → worker-4LZGRHDA.js.map} +0 -0
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
HttpMcpToolExecutor,
|
|
57
57
|
InternalScheduledArtifactRenderRequestSchema,
|
|
58
58
|
ListScheduledRunsQuerySchema,
|
|
59
|
+
LocalSourcebookSchemaTypeSchema,
|
|
59
60
|
PublicScheduledRunTokenSchema,
|
|
60
61
|
RenewEditorialReadingRoomDownloadInputSchema,
|
|
61
62
|
SCHEDULED_ARTIFACT_TEMPLATE_PRESET,
|
|
@@ -72,11 +73,13 @@ import {
|
|
|
72
73
|
configureReportSaving,
|
|
73
74
|
createConnectedDataArtifact,
|
|
74
75
|
createDirectoryCsvArtifact,
|
|
76
|
+
defaultLocalSourcebookSchemaType,
|
|
75
77
|
exportFanout,
|
|
76
78
|
handleMcpTasksHttpRequest,
|
|
77
79
|
harvestTimeoutBudget,
|
|
78
80
|
hashOwnerId,
|
|
79
81
|
isLoopbackBaseUrl,
|
|
82
|
+
localSourcebookSchemaContract,
|
|
80
83
|
outputBaseDir,
|
|
81
84
|
registerBrowserAgentMcpTools,
|
|
82
85
|
registerMemoryMcpTools,
|
|
@@ -85,8 +88,9 @@ import {
|
|
|
85
88
|
renewConnectedDataArtifactDownload,
|
|
86
89
|
renewDirectoryArtifactDownload,
|
|
87
90
|
resolveDeploymentProfile,
|
|
91
|
+
resolveLocalSourcebookSchemaType,
|
|
88
92
|
transcribeMediaUrl
|
|
89
|
-
} from "./chunk-
|
|
93
|
+
} from "./chunk-FQIICRR4.js";
|
|
90
94
|
import {
|
|
91
95
|
auditImageUrls,
|
|
92
96
|
auditImages,
|
|
@@ -142,7 +146,7 @@ import {
|
|
|
142
146
|
} from "./chunk-3ZUBQQPQ.js";
|
|
143
147
|
import {
|
|
144
148
|
PACKAGE_VERSION
|
|
145
|
-
} from "./chunk-
|
|
149
|
+
} from "./chunk-CEZ3NFBP.js";
|
|
146
150
|
import {
|
|
147
151
|
abandonExtractSettlement,
|
|
148
152
|
countSuccessfulPages,
|
|
@@ -165,7 +169,7 @@ import {
|
|
|
165
169
|
setExtractJobTotal,
|
|
166
170
|
settleExtractJob,
|
|
167
171
|
terminalExtractJobStatus
|
|
168
|
-
} from "./chunk-
|
|
172
|
+
} from "./chunk-JFOT5JEL.js";
|
|
169
173
|
import {
|
|
170
174
|
BROWSER_OPEN_MIN_BALANCE_MC,
|
|
171
175
|
CONCURRENCY_PRICE_ID,
|
|
@@ -178,6 +182,7 @@ import {
|
|
|
178
182
|
CONNECTED_USAGE_RATE_POLICY,
|
|
179
183
|
CONNECTED_USAGE_RATE_POLICY_VERSION,
|
|
180
184
|
CREDIT_COST_CATALOG,
|
|
185
|
+
LISTING_WRITE_RATE_POLICY,
|
|
181
186
|
LedgerOperation,
|
|
182
187
|
MC_COSTS,
|
|
183
188
|
MC_PER_CREDIT,
|
|
@@ -213,7 +218,7 @@ import {
|
|
|
213
218
|
tierChangeLeavesPrivateOffer,
|
|
214
219
|
tierCreditsPerInterval,
|
|
215
220
|
vendorCostUsdToMc
|
|
216
|
-
} from "./chunk-
|
|
221
|
+
} from "./chunk-SOEYDWJU.js";
|
|
217
222
|
import {
|
|
218
223
|
CaptchaError,
|
|
219
224
|
MapsSearchIncompletePageError,
|
|
@@ -8667,7 +8672,7 @@ var siteExtractFn = inngest.createFunction(
|
|
|
8667
8672
|
});
|
|
8668
8673
|
}) : null;
|
|
8669
8674
|
const finalized = await step.run("finalize", async () => {
|
|
8670
|
-
const { assembleExtractArtifacts } = await import("./extract-bundle-
|
|
8675
|
+
const { assembleExtractArtifacts } = await import("./extract-bundle-DYMQRQWG.js");
|
|
8671
8676
|
const current = await getExtractJob(jobId2);
|
|
8672
8677
|
if (!current) throw new Error("extract job disappeared before finalization");
|
|
8673
8678
|
const stored = await assembleExtractArtifacts(current, { branding, imageAudit });
|
|
@@ -12033,16 +12038,18 @@ async function createLocalSourcebookSubmission(input) {
|
|
|
12033
12038
|
const existing = await db.execute({ sql: `SELECT * FROM local_sourcebook_submissions WHERE owner_user_id = ? AND idempotency_key = ? LIMIT 1`, args: [input.ownerUserId, input.idempotencyKey] });
|
|
12034
12039
|
if (existing.rows[0]) {
|
|
12035
12040
|
const submission = mapSubmission(existing.rows[0]);
|
|
12036
|
-
if (submission.category !== input.category || submission.state !== input.state || submission.slug !== input.slug || submission.businessName !== input.businessName || submission.websiteUrl !== input.websiteUrl) throw new Error("Local Sourcebook idempotency key conflicts with a different listing identity.");
|
|
12041
|
+
if (submission.category !== input.category || submission.state !== input.state || submission.slug !== input.slug || submission.businessName !== input.businessName || submission.websiteUrl !== input.websiteUrl || input.schemaOrgType != null && submission.coverage.schemaOrgType !== input.schemaOrgType) throw new Error("Local Sourcebook idempotency key conflicts with a different listing identity.");
|
|
12037
12042
|
return submission;
|
|
12038
12043
|
}
|
|
12039
12044
|
}
|
|
12040
12045
|
const id = `lsb_${randomUUID3().replace(/-/g, "")}`;
|
|
12041
12046
|
const coverage = {
|
|
12042
|
-
requested: ["website_crawl", "services_products", "service_areas", "genuine_images", "staff_team", "review_sources"],
|
|
12047
|
+
requested: ["website_crawl", "structured_data", "services_products", "service_areas", "genuine_images", "staff_team", "review_sources"],
|
|
12043
12048
|
crawl: { state: "queued", pagesDiscovered: 0, pagesCaptured: 0 },
|
|
12044
12049
|
media: { state: "queued", genuineImagesCaptured: 0 },
|
|
12045
|
-
reviews: { state: "queued", captured: 0, availableReported: null, stopReason: null }
|
|
12050
|
+
reviews: { state: "queued", captured: 0, availableReported: null, stopReason: null },
|
|
12051
|
+
...input.schemaOrgType ? { schemaOrgType: input.schemaOrgType } : {},
|
|
12052
|
+
...input.billing ? { billing: { mode: "fixed_listing_write", ...input.billing } } : {}
|
|
12046
12053
|
};
|
|
12047
12054
|
await db.batch([
|
|
12048
12055
|
{ sql: `INSERT INTO local_sourcebook_submissions (id, owner_user_id, category, state, slug, business_name, website_url, coverage_json, idempotency_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, args: [id, input.ownerUserId, input.category, input.state, input.slug, input.businessName, input.websiteUrl, JSON.stringify(coverage), input.idempotencyKey ?? null] },
|
|
@@ -12062,13 +12069,17 @@ async function getLocalSourcebookDraft(id, ownerUserId) {
|
|
|
12062
12069
|
const result = await getDb().execute({ sql: `SELECT payload_json FROM local_sourcebook_revisions WHERE submission_id = ? AND revision = ?`, args: [id, submission.draftRevision] });
|
|
12063
12070
|
return result.rows[0] ? parseJson2(result.rows[0].payload_json) : null;
|
|
12064
12071
|
}
|
|
12065
|
-
async function queueLocalSourcebookRefresh(id, ownerUserId) {
|
|
12072
|
+
async function queueLocalSourcebookRefresh(id, ownerUserId, billing) {
|
|
12066
12073
|
const existing = await getLocalSourcebookSubmission(id, ownerUserId);
|
|
12067
12074
|
if (!existing) return null;
|
|
12068
12075
|
if (existing.status === "rejected" || existing.status === "unpublished") {
|
|
12069
12076
|
throw new Error(`A ${existing.status} Local Sourcebook listing cannot be refreshed by its owner.`);
|
|
12070
12077
|
}
|
|
12071
|
-
const coverage = {
|
|
12078
|
+
const coverage = {
|
|
12079
|
+
...existing.coverage,
|
|
12080
|
+
refreshRequestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12081
|
+
...billing ? { billing: { mode: "fixed_listing_write", ...billing } } : {}
|
|
12082
|
+
};
|
|
12072
12083
|
await getDb().execute({ sql: `UPDATE local_sourcebook_submissions SET status = 'queued', coverage_json = ?, acquisition_claimed_at = NULL, acquisition_error = NULL, updated_at = datetime('now') WHERE id = ? AND owner_user_id = ?`, args: [JSON.stringify(coverage), id, ownerUserId] });
|
|
12073
12084
|
await event(id, "enrichment.queued", "owner", String(ownerUserId));
|
|
12074
12085
|
return getLocalSourcebookSubmission(id, ownerUserId);
|
|
@@ -12774,17 +12785,100 @@ var MapsExtractor = class {
|
|
|
12774
12785
|
}
|
|
12775
12786
|
};
|
|
12776
12787
|
|
|
12788
|
+
// src/api/listing-write-billing.ts
|
|
12789
|
+
function operationFor(surface, kind) {
|
|
12790
|
+
if (surface === "local_sourcebook") {
|
|
12791
|
+
return kind === "create" ? LedgerOperation.LOCAL_SOURCEBOOK_CREATE : LedgerOperation.LOCAL_SOURCEBOOK_EDIT;
|
|
12792
|
+
}
|
|
12793
|
+
return kind === "create" ? LedgerOperation.COMMONS_CREATE : LedgerOperation.COMMONS_EDIT;
|
|
12794
|
+
}
|
|
12795
|
+
function refundOperationFor(surface, kind) {
|
|
12796
|
+
if (surface === "local_sourcebook") {
|
|
12797
|
+
return kind === "create" ? LedgerOperation.LOCAL_SOURCEBOOK_CREATE_REFUND : LedgerOperation.LOCAL_SOURCEBOOK_EDIT_REFUND;
|
|
12798
|
+
}
|
|
12799
|
+
return kind === "create" ? LedgerOperation.COMMONS_CREATE_REFUND : LedgerOperation.COMMONS_EDIT_REFUND;
|
|
12800
|
+
}
|
|
12801
|
+
function listingWritePrice(kind) {
|
|
12802
|
+
return LISTING_WRITE_RATE_POLICY[kind];
|
|
12803
|
+
}
|
|
12804
|
+
function listingWriteDebitKey(surface, userId, idempotencyKey3) {
|
|
12805
|
+
return `listing-write:${surface}:${userId}:${idempotencyKey3}`;
|
|
12806
|
+
}
|
|
12807
|
+
function publicListingWriteBilling(receipt) {
|
|
12808
|
+
return {
|
|
12809
|
+
surface: receipt.surface,
|
|
12810
|
+
operation: receipt.kind,
|
|
12811
|
+
credits: receipt.credits,
|
|
12812
|
+
charged: !receipt.duplicate,
|
|
12813
|
+
idempotentReplay: receipt.duplicate,
|
|
12814
|
+
balanceCredits: receipt.balanceMc / MC_PER_CREDIT,
|
|
12815
|
+
acquisitionIncluded: receipt.surface === "local_sourcebook"
|
|
12816
|
+
};
|
|
12817
|
+
}
|
|
12818
|
+
async function chargeListingWrite(input) {
|
|
12819
|
+
const price = listingWritePrice(input.kind);
|
|
12820
|
+
const debitKey2 = listingWriteDebitKey(input.surface, input.userId, input.idempotencyKey);
|
|
12821
|
+
const debit = await debitMcIdempotent(
|
|
12822
|
+
input.userId,
|
|
12823
|
+
price.mc,
|
|
12824
|
+
operationFor(input.surface, input.kind),
|
|
12825
|
+
`${input.surface}:${input.kind}`,
|
|
12826
|
+
debitKey2
|
|
12827
|
+
);
|
|
12828
|
+
if (!debit.ok) return { insufficient: insufficientBalanceResponse(debit.balance_mc, price.mc) };
|
|
12829
|
+
return {
|
|
12830
|
+
surface: input.surface,
|
|
12831
|
+
kind: input.kind,
|
|
12832
|
+
userId: input.userId,
|
|
12833
|
+
credits: price.credits,
|
|
12834
|
+
amountMc: price.mc,
|
|
12835
|
+
debitKey: debitKey2,
|
|
12836
|
+
duplicate: debit.duplicate,
|
|
12837
|
+
balanceMc: debit.balance_mc
|
|
12838
|
+
};
|
|
12839
|
+
}
|
|
12840
|
+
async function refundListingWrite(receipt, reason) {
|
|
12841
|
+
if (receipt.duplicate) return null;
|
|
12842
|
+
return settleDebitMcIdempotent(
|
|
12843
|
+
receipt.userId,
|
|
12844
|
+
receipt.debitKey,
|
|
12845
|
+
0,
|
|
12846
|
+
refundOperationFor(receipt.surface, receipt.kind),
|
|
12847
|
+
reason,
|
|
12848
|
+
receipt.surface
|
|
12849
|
+
);
|
|
12850
|
+
}
|
|
12851
|
+
async function refundPersistedListingWrite(input) {
|
|
12852
|
+
return settleDebitMcIdempotent(
|
|
12853
|
+
input.userId,
|
|
12854
|
+
input.debitKey,
|
|
12855
|
+
0,
|
|
12856
|
+
refundOperationFor(input.surface, input.kind),
|
|
12857
|
+
input.reason,
|
|
12858
|
+
input.surface
|
|
12859
|
+
);
|
|
12860
|
+
}
|
|
12861
|
+
|
|
12777
12862
|
// src/api/local-sourcebook-compiler.ts
|
|
12778
12863
|
import { createHash as createHash3 } from "crypto";
|
|
12779
12864
|
|
|
12780
12865
|
// src/api/local-sourcebook-public-urls.ts
|
|
12781
12866
|
var DEFAULT_ROOT_DOMAIN = "localsourcebook.com";
|
|
12867
|
+
var CATEGORY_PUBLIC_SLUGS = {
|
|
12868
|
+
home: "home-services",
|
|
12869
|
+
professional: "professional",
|
|
12870
|
+
restaurants: "restaurants",
|
|
12871
|
+
financial: "financial",
|
|
12872
|
+
realestate: "realestate",
|
|
12873
|
+
auto: "auto",
|
|
12874
|
+
wellness: "wellness"
|
|
12875
|
+
};
|
|
12782
12876
|
function rootDomain() {
|
|
12783
12877
|
const configured = process.env.LOCAL_SOURCEBOOK_PUBLIC_ROOT_DOMAIN?.trim() || DEFAULT_ROOT_DOMAIN;
|
|
12784
12878
|
return configured.replace(/^https?:\/\//i, "").replace(/^www\./i, "").replace(/\/.*$/, "").replace(/\.+$/, "").toLowerCase();
|
|
12785
12879
|
}
|
|
12786
12880
|
function localSourcebookPublicUrls(category, state, slug2) {
|
|
12787
|
-
const categoryUrl = `https://${category}.${rootDomain()}`;
|
|
12881
|
+
const categoryUrl = `https://${CATEGORY_PUBLIC_SLUGS[category]}.${rootDomain()}`;
|
|
12788
12882
|
const statePath = `/${state.toLowerCase()}`;
|
|
12789
12883
|
const profilePath = `${statePath}/${slug2}/`;
|
|
12790
12884
|
const reviewsPath = `${statePath}/${slug2}/reviews`;
|
|
@@ -13091,6 +13185,65 @@ function reviewStopReason(input, captured, exposed) {
|
|
|
13091
13185
|
if (exposed && captured < exposed) return `Google Maps stopped exposing additional complete review cards after ${captured} of ${exposed} reported reviews.`;
|
|
13092
13186
|
return `All ${captured} complete, unique review cards exposed during this acquisition were retained.`;
|
|
13093
13187
|
}
|
|
13188
|
+
function buildLocalBusinessJsonLd(input) {
|
|
13189
|
+
const businessId = `${input.profileUrl}#business`;
|
|
13190
|
+
const business = {
|
|
13191
|
+
"@type": input.schemaOrgType === "LocalBusiness" ? "LocalBusiness" : ["LocalBusiness", input.schemaOrgType],
|
|
13192
|
+
"@id": businessId,
|
|
13193
|
+
url: input.profileUrl,
|
|
13194
|
+
name: input.name,
|
|
13195
|
+
description: input.description,
|
|
13196
|
+
...input.phone ? { telephone: input.phone } : {},
|
|
13197
|
+
...input.address || input.locality || input.region || input.postalCode ? {
|
|
13198
|
+
address: {
|
|
13199
|
+
"@type": "PostalAddress",
|
|
13200
|
+
...input.address ? { streetAddress: input.address } : {},
|
|
13201
|
+
...input.locality ? { addressLocality: input.locality } : {},
|
|
13202
|
+
...input.region ? { addressRegion: input.region } : {},
|
|
13203
|
+
...input.postalCode ? { postalCode: input.postalCode } : {},
|
|
13204
|
+
addressCountry: "US"
|
|
13205
|
+
}
|
|
13206
|
+
} : {},
|
|
13207
|
+
...Number.isFinite(input.latitude) && Number.isFinite(input.longitude) ? {
|
|
13208
|
+
geo: { "@type": "GeoCoordinates", latitude: input.latitude, longitude: input.longitude }
|
|
13209
|
+
} : {},
|
|
13210
|
+
...input.images.length ? { image: input.images } : {},
|
|
13211
|
+
...input.serviceAreas.length ? { areaServed: input.serviceAreas } : {},
|
|
13212
|
+
...input.services.length ? {
|
|
13213
|
+
hasOfferCatalog: {
|
|
13214
|
+
"@type": "OfferCatalog",
|
|
13215
|
+
name: "Services",
|
|
13216
|
+
itemListElement: input.services.map((name) => ({ "@type": "Offer", itemOffered: { "@type": "Service", name } }))
|
|
13217
|
+
}
|
|
13218
|
+
} : {},
|
|
13219
|
+
...Number.isFinite(input.rating) && Number.isFinite(input.ratingCount) && Number(input.ratingCount) > 0 ? {
|
|
13220
|
+
aggregateRating: { "@type": "AggregateRating", ratingValue: input.rating, reviewCount: input.ratingCount, bestRating: 5, worstRating: 1 }
|
|
13221
|
+
} : {},
|
|
13222
|
+
sameAs: unique([input.website, input.mapsUrl], 10)
|
|
13223
|
+
};
|
|
13224
|
+
const graph = [business, {
|
|
13225
|
+
"@type": "BreadcrumbList",
|
|
13226
|
+
"@id": `${input.profileUrl}#breadcrumbs`,
|
|
13227
|
+
itemListElement: [
|
|
13228
|
+
{ "@type": "ListItem", position: 1, name: "Local Sourcebook", item: "https://localsourcebook.com/" },
|
|
13229
|
+
{ "@type": "ListItem", position: 2, name: input.region || "State directory", item: input.stateUrl },
|
|
13230
|
+
{ "@type": "ListItem", position: 3, name: input.name, item: input.profileUrl }
|
|
13231
|
+
]
|
|
13232
|
+
}];
|
|
13233
|
+
if (input.faq.length) {
|
|
13234
|
+
graph.push({
|
|
13235
|
+
"@type": "FAQPage",
|
|
13236
|
+
"@id": `${input.profileUrl}#faq`,
|
|
13237
|
+
about: { "@id": businessId },
|
|
13238
|
+
mainEntity: input.faq.map((item) => ({
|
|
13239
|
+
"@type": "Question",
|
|
13240
|
+
name: item.question,
|
|
13241
|
+
acceptedAnswer: { "@type": "Answer", text: item.answer }
|
|
13242
|
+
}))
|
|
13243
|
+
});
|
|
13244
|
+
}
|
|
13245
|
+
return { "@context": "https://schema.org", "@graph": graph };
|
|
13246
|
+
}
|
|
13094
13247
|
function compileLocalSourcebookListing(input) {
|
|
13095
13248
|
const collectedAt = input.collectedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
13096
13249
|
const pages = successfulPages(input.site);
|
|
@@ -13160,6 +13313,33 @@ function compileLocalSourcebookListing(input) {
|
|
|
13160
13313
|
}
|
|
13161
13314
|
];
|
|
13162
13315
|
const publicUrls = localSourcebookPublicUrls(input.submission.category, input.submission.state, input.submission.slug);
|
|
13316
|
+
const schemaOrgType = resolveLocalSourcebookSchemaType(input.submission.category, input.submission.coverage.schemaOrgType);
|
|
13317
|
+
const listingName = maps?.name || input.submission.businessName;
|
|
13318
|
+
const listingWebsite = maps?.website || input.submission.websiteUrl;
|
|
13319
|
+
const listingMapsUrl = maps?.placeUrl || maps?.cidUrl || void 0;
|
|
13320
|
+
const stateUrl = new URL(`/${input.submission.state.toLowerCase()}`, publicUrls.profileUrl).href;
|
|
13321
|
+
const jsonLd = buildLocalBusinessJsonLd({
|
|
13322
|
+
schemaOrgType,
|
|
13323
|
+
profileUrl: publicUrls.profileUrl,
|
|
13324
|
+
stateUrl,
|
|
13325
|
+
name: listingName,
|
|
13326
|
+
description: summary,
|
|
13327
|
+
address: location2.address,
|
|
13328
|
+
locality: location2.locality,
|
|
13329
|
+
region: location2.region,
|
|
13330
|
+
postalCode: location2.postalCode,
|
|
13331
|
+
phone: maps?.phoneDisplay || maps?.phone || void 0,
|
|
13332
|
+
website: listingWebsite,
|
|
13333
|
+
mapsUrl: listingMapsUrl,
|
|
13334
|
+
latitude: maps?.lat ?? void 0,
|
|
13335
|
+
longitude: maps?.lng ?? void 0,
|
|
13336
|
+
services,
|
|
13337
|
+
serviceAreas,
|
|
13338
|
+
images: media.map((item) => String(item.src)),
|
|
13339
|
+
rating: Number.isFinite(rating) ? rating : void 0,
|
|
13340
|
+
ratingCount: Number.isFinite(exposedCount) ? exposedCount : void 0,
|
|
13341
|
+
faq
|
|
13342
|
+
});
|
|
13163
13343
|
const listing = {
|
|
13164
13344
|
id: input.submission.id,
|
|
13165
13345
|
publicationRevision: input.submission.draftRevision + 1,
|
|
@@ -13176,7 +13356,9 @@ function compileLocalSourcebookListing(input) {
|
|
|
13176
13356
|
profile: publicUrls.profileUrl,
|
|
13177
13357
|
reviews: publicUrls.reviewsUrl
|
|
13178
13358
|
},
|
|
13179
|
-
name:
|
|
13359
|
+
name: listingName,
|
|
13360
|
+
schemaOrgType,
|
|
13361
|
+
jsonLd,
|
|
13180
13362
|
summary,
|
|
13181
13363
|
description,
|
|
13182
13364
|
address: location2.address,
|
|
@@ -13184,8 +13366,8 @@ function compileLocalSourcebookListing(input) {
|
|
|
13184
13366
|
region: location2.region,
|
|
13185
13367
|
postalCode: location2.postalCode,
|
|
13186
13368
|
phone: maps?.phoneDisplay || maps?.phone || void 0,
|
|
13187
|
-
website:
|
|
13188
|
-
mapsUrl:
|
|
13369
|
+
website: listingWebsite,
|
|
13370
|
+
mapsUrl: listingMapsUrl,
|
|
13189
13371
|
latitude: maps?.lat ?? void 0,
|
|
13190
13372
|
longitude: maps?.lng ?? void 0,
|
|
13191
13373
|
hours,
|
|
@@ -13225,11 +13407,12 @@ function compileLocalSourcebookListing(input) {
|
|
|
13225
13407
|
return {
|
|
13226
13408
|
listing,
|
|
13227
13409
|
coverage: {
|
|
13228
|
-
requested: ["website_crawl", "services_products", "service_areas", "genuine_images", "staff_team", "review_sources"],
|
|
13410
|
+
requested: ["website_crawl", "structured_data", "services_products", "service_areas", "genuine_images", "staff_team", "review_sources"],
|
|
13229
13411
|
crawl: { state: input.siteError ? "failed" : input.site?.truncated ? "partial" : "complete", pagesDiscovered: input.site?.spider.totalFound ?? 0, pagesCaptured: pages.length, requestedMaximum: input.requestedMaxPages, error: input.siteError ?? null },
|
|
13230
13412
|
media: { state: input.siteError ? "failed" : "complete", genuineImagesCaptured: media.length, teamImagesCaptured: media.filter((item) => item.kind === "team").length },
|
|
13231
13413
|
reviews: { state: input.mapsError || input.reviewBillingLimited ? "partial" : "complete", captured: reviews.length, availableReported: Number.isFinite(exposedCount) ? exposedCount : null, requestedMaximum: input.requestedMaxReviews, stopReason: reviewStopReason(input, reviews.length, Number.isFinite(exposedCount) ? exposedCount : void 0) },
|
|
13232
13414
|
maps: { state: maps ? "complete" : "failed", exactPlaceUrl: maps?.placeUrl ?? null, cid: maps?.cidDecimal ?? maps?.cid ?? null, error: input.mapsError ?? null },
|
|
13415
|
+
structuredData: { state: "complete", rootType: "LocalBusiness", schemaOrgType, graphNodes: jsonLd["@graph"].length },
|
|
13233
13416
|
compiledAt: collectedAt
|
|
13234
13417
|
}
|
|
13235
13418
|
};
|
|
@@ -13314,110 +13497,83 @@ async function runLocalSourcebookAcquisition(submission, provider = createDefaul
|
|
|
13314
13497
|
const user = await getUserById(submission.ownerUserId);
|
|
13315
13498
|
if (!user) throw new Error("The submission owner is no longer active.");
|
|
13316
13499
|
if (!user.subscription_tier) throw new Error("An active MCP Scraper subscription is required for acquisition.");
|
|
13317
|
-
const
|
|
13318
|
-
const
|
|
13319
|
-
const
|
|
13320
|
-
|
|
13321
|
-
|
|
13322
|
-
|
|
13323
|
-
|
|
13324
|
-
|
|
13325
|
-
|
|
13326
|
-
|
|
13327
|
-
|
|
13328
|
-
|
|
13500
|
+
const storedBilling = submission.coverage.billing;
|
|
13501
|
+
const inferredKind = submission.publishedRevision == null ? "create" : "edit";
|
|
13502
|
+
const kind = storedBilling?.kind === "edit" ? "edit" : storedBilling?.kind === "create" ? "create" : inferredKind;
|
|
13503
|
+
const price = listingWritePrice(kind);
|
|
13504
|
+
let debitKey2 = typeof storedBilling?.debitKey === "string" ? storedBilling.debitKey : "";
|
|
13505
|
+
if (!debitKey2) {
|
|
13506
|
+
const fallback = await chargeListingWrite({
|
|
13507
|
+
surface: "local_sourcebook",
|
|
13508
|
+
kind,
|
|
13509
|
+
userId: submission.ownerUserId,
|
|
13510
|
+
idempotencyKey: `worker:${submission.id}:revision:${submission.draftRevision}`
|
|
13511
|
+
});
|
|
13512
|
+
if ("insufficient" in fallback) throw new Error(fallback.insufficient.message);
|
|
13513
|
+
debitKey2 = fallback.debitKey;
|
|
13514
|
+
}
|
|
13515
|
+
const maxPages = LOCAL_SOURCEBOOK_MAX_PAGES;
|
|
13329
13516
|
let site = null;
|
|
13330
13517
|
let maps = null;
|
|
13331
13518
|
let siteError = null;
|
|
13332
13519
|
let mapsError = null;
|
|
13333
|
-
let reviewBillingLimited = false;
|
|
13334
|
-
let siteHoldMc = 0;
|
|
13335
|
-
let siteActualMc = 0;
|
|
13336
|
-
let mapsRefunded = false;
|
|
13337
|
-
let reviewsBilledMc = 0;
|
|
13338
|
-
if (maxPages < 1) {
|
|
13339
|
-
siteError = "The subscriber balance covered exact-place acquisition but not a website page.";
|
|
13340
|
-
} else {
|
|
13341
|
-
siteHoldMc = maxPages * MC_COSTS.page_scrape;
|
|
13342
|
-
const siteDescription = `Local Sourcebook: ${new URL(submission.websiteUrl).hostname}`;
|
|
13343
|
-
const siteHold = await debitMcIdempotent(
|
|
13344
|
-
submission.ownerUserId,
|
|
13345
|
-
siteHoldMc,
|
|
13346
|
-
LedgerOperation.EXTRACT_SITE_HOLD,
|
|
13347
|
-
siteDescription,
|
|
13348
|
-
`${billingCycle}:website-hold`
|
|
13349
|
-
);
|
|
13350
|
-
if (!siteHold.ok) {
|
|
13351
|
-
siteError = "The subscriber balance changed before the website acquisition hold could be placed.";
|
|
13352
|
-
siteHoldMc = 0;
|
|
13353
|
-
} else {
|
|
13354
|
-
try {
|
|
13355
|
-
site = await provider.extractWebsite(submission, maxPages);
|
|
13356
|
-
const successfulPages2 = site.pages.filter(isPageExtractionSuccessful).length;
|
|
13357
|
-
siteActualMc = successfulPages2 * MC_COSTS.page_scrape;
|
|
13358
|
-
const refund = siteHoldMc - siteActualMc;
|
|
13359
|
-
if (refund > 0) await creditMcIdempotent(submission.ownerUserId, refund, LedgerOperation.EXTRACT_SITE_REFUND, `Local Sourcebook unused website-page hold: ${submission.id}`, `${billingCycle}:website-unused-refund`);
|
|
13360
|
-
await logRequestEvent({ userId: submission.ownerUserId, source: "local_sourcebook_website", status: "done", query: submission.websiteUrl, resultCount: successfulPages2, result: { submissionId: submission.id, discovered: site.spider.totalFound, captured: successfulPages2, truncated: site.truncated } });
|
|
13361
|
-
} catch (error) {
|
|
13362
|
-
siteError = errorMessage2(error);
|
|
13363
|
-
await creditMcIdempotent(submission.ownerUserId, siteHoldMc, LedgerOperation.EXTRACT_SITE_REFUND, `Local Sourcebook failed website acquisition: ${submission.id}`, `${billingCycle}:website-failure-refund`);
|
|
13364
|
-
siteHoldMc = 0;
|
|
13365
|
-
await logRequestEvent({ userId: submission.ownerUserId, source: "local_sourcebook_website", status: "failed", query: submission.websiteUrl, error: siteError });
|
|
13366
|
-
}
|
|
13367
|
-
}
|
|
13368
|
-
}
|
|
13369
13520
|
try {
|
|
13370
|
-
|
|
13371
|
-
|
|
13372
|
-
|
|
13373
|
-
|
|
13374
|
-
|
|
13375
|
-
|
|
13376
|
-
|
|
13377
|
-
reviewCost,
|
|
13378
|
-
LedgerOperation.MAPS_REVIEW,
|
|
13379
|
-
`Local Sourcebook reviews: ${submission.businessName}`,
|
|
13380
|
-
`${billingCycle}:maps-reviews`
|
|
13381
|
-
);
|
|
13382
|
-
if (debit.ok) {
|
|
13383
|
-
reviewsBilledMc = reviewCost;
|
|
13384
|
-
} else {
|
|
13385
|
-
reviewBillingLimited = true;
|
|
13386
|
-
maps.reviews = [];
|
|
13387
|
-
}
|
|
13521
|
+
try {
|
|
13522
|
+
site = await provider.extractWebsite(submission, maxPages);
|
|
13523
|
+
const successfulPages2 = site.pages.filter(isPageExtractionSuccessful).length;
|
|
13524
|
+
await logRequestEvent({ userId: submission.ownerUserId, source: "local_sourcebook_website", status: "done", query: submission.websiteUrl, resultCount: successfulPages2, result: { submissionId: submission.id, discovered: site.spider.totalFound, captured: successfulPages2, truncated: site.truncated } });
|
|
13525
|
+
} catch (error) {
|
|
13526
|
+
siteError = errorMessage2(error);
|
|
13527
|
+
await logRequestEvent({ userId: submission.ownerUserId, source: "local_sourcebook_website", status: "failed", query: submission.websiteUrl, error: siteError });
|
|
13388
13528
|
}
|
|
13389
|
-
|
|
13529
|
+
try {
|
|
13530
|
+
maps = await provider.extractMaps(submission, LOCAL_SOURCEBOOK_MAX_REVIEWS);
|
|
13531
|
+
validateLocalSourcebookMapsIdentity(submission, maps);
|
|
13532
|
+
await logRequestEvent({ userId: submission.ownerUserId, source: "local_sourcebook_maps", status: "done", query: submission.businessName, location: submission.state.toUpperCase(), resultCount: maps.reviews.length, result: { submissionId: submission.id, placeUrl: maps.placeUrl, cid: maps.cidDecimal ?? maps.cid, reviewsStatus: maps.reviewsStatus, reviewsRetained: maps.reviews.length, services: maps.services.length, areasServed: maps.areasServed.length } });
|
|
13533
|
+
} catch (error) {
|
|
13534
|
+
mapsError = errorMessage2(error);
|
|
13535
|
+
maps = null;
|
|
13536
|
+
await logRequestEvent({ userId: submission.ownerUserId, source: "local_sourcebook_maps", status: "failed", query: submission.businessName, location: submission.state.toUpperCase(), error: mapsError });
|
|
13537
|
+
}
|
|
13538
|
+
if (!maps) throw new Error(`Exact-place acquisition is required before publication. Maps: ${mapsError || "not available"}. Website: ${siteError || (site ? "captured" : "not available")}.`);
|
|
13539
|
+
const compiled = compileLocalSourcebookListing({
|
|
13540
|
+
submission,
|
|
13541
|
+
site,
|
|
13542
|
+
maps,
|
|
13543
|
+
requestedMaxPages: maxPages,
|
|
13544
|
+
requestedMaxReviews: LOCAL_SOURCEBOOK_MAX_REVIEWS,
|
|
13545
|
+
siteError,
|
|
13546
|
+
mapsError,
|
|
13547
|
+
reviewBillingLimited: false
|
|
13548
|
+
});
|
|
13549
|
+
const coverage = {
|
|
13550
|
+
...compiled.coverage,
|
|
13551
|
+
billing: {
|
|
13552
|
+
mode: "fixed_listing_write",
|
|
13553
|
+
kind,
|
|
13554
|
+
credits: price.credits,
|
|
13555
|
+
amountMc: price.mc,
|
|
13556
|
+
debitKey: debitKey2,
|
|
13557
|
+
acquisitionIncluded: true
|
|
13558
|
+
}
|
|
13559
|
+
};
|
|
13560
|
+
const completed = await completeLocalSourcebookAcquisition(submission.id, compiled.listing, coverage, submission.draftRevision);
|
|
13561
|
+
if (!completed) throw new Error("The Local Sourcebook draft could not be saved after acquisition.");
|
|
13562
|
+
return completed;
|
|
13390
13563
|
} catch (error) {
|
|
13391
|
-
|
|
13392
|
-
|
|
13393
|
-
|
|
13394
|
-
|
|
13395
|
-
|
|
13396
|
-
|
|
13397
|
-
|
|
13398
|
-
|
|
13399
|
-
|
|
13400
|
-
|
|
13401
|
-
maps,
|
|
13402
|
-
requestedMaxPages: maxPages,
|
|
13403
|
-
requestedMaxReviews: LOCAL_SOURCEBOOK_MAX_REVIEWS,
|
|
13404
|
-
siteError,
|
|
13405
|
-
mapsError,
|
|
13406
|
-
reviewBillingLimited
|
|
13407
|
-
});
|
|
13408
|
-
const coverage = {
|
|
13409
|
-
...compiled.coverage,
|
|
13410
|
-
billing: {
|
|
13411
|
-
websiteHeldMc: siteHoldMc,
|
|
13412
|
-
websiteUsedMc: siteActualMc,
|
|
13413
|
-
mapsUsedMc: mapsRefunded ? 0 : MC_COSTS.maps_place,
|
|
13414
|
-
reviewsUsedMc: reviewsBilledMc,
|
|
13415
|
-
reviewBillingLimited
|
|
13564
|
+
const current = await getLocalSourcebookSubmission(submission.id).catch(() => null);
|
|
13565
|
+
const revisionAlreadyPublished = current?.status === "published" && current.publishedRevision != null && current.draftRevision > submission.draftRevision;
|
|
13566
|
+
if (!revisionAlreadyPublished) {
|
|
13567
|
+
await refundPersistedListingWrite({
|
|
13568
|
+
surface: "local_sourcebook",
|
|
13569
|
+
kind,
|
|
13570
|
+
userId: submission.ownerUserId,
|
|
13571
|
+
debitKey: debitKey2,
|
|
13572
|
+
reason: `Local Sourcebook acquisition failed: ${errorMessage2(error)}`
|
|
13573
|
+
}).catch(() => null);
|
|
13416
13574
|
}
|
|
13417
|
-
|
|
13418
|
-
|
|
13419
|
-
if (!completed) throw new Error("The Local Sourcebook draft could not be saved after acquisition.");
|
|
13420
|
-
return completed;
|
|
13575
|
+
throw error;
|
|
13576
|
+
}
|
|
13421
13577
|
}
|
|
13422
13578
|
async function processLocalSourcebookSubmission(submissionId) {
|
|
13423
13579
|
const submission = await claimLocalSourcebookAcquisition(submissionId);
|
|
@@ -21313,6 +21469,7 @@ var DirectoryIdentitySchema = z25.object({
|
|
|
21313
21469
|
state: z25.string().trim().length(2).transform((value) => value.toLowerCase()),
|
|
21314
21470
|
businessName: z25.string().trim().min(2).max(160),
|
|
21315
21471
|
websiteUrl: z25.url().refine((value) => /^https?:\/\//.test(value), "websiteUrl must use http or https"),
|
|
21472
|
+
schemaOrgType: LocalSourcebookSchemaTypeSchema.optional(),
|
|
21316
21473
|
slug: z25.string().trim().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).max(100).optional(),
|
|
21317
21474
|
tags: z25.array(z25.string().trim().min(1).max(60)).max(20).default([]),
|
|
21318
21475
|
idempotencyKey: z25.string().trim().min(8).max(200)
|
|
@@ -21397,7 +21554,7 @@ async function resolveLocalSourcebookTags(candidates) {
|
|
|
21397
21554
|
function getLocalSourcebookContract(category) {
|
|
21398
21555
|
return {
|
|
21399
21556
|
name: "Local Sourcebook listing",
|
|
21400
|
-
version: "
|
|
21557
|
+
version: "local_sourcebook_listing_v3",
|
|
21401
21558
|
purpose: "An owner-scoped, evidence-backed local business record with a separate public profile and complete captured-review archive.",
|
|
21402
21559
|
categories: LOCAL_SOURCEBOOK_CATEGORIES,
|
|
21403
21560
|
selectedCategory: category ?? null,
|
|
@@ -21408,11 +21565,14 @@ function getLocalSourcebookContract(category) {
|
|
|
21408
21565
|
reviews: "https://{category}.localsourcebook.com/{state}/{business-slug}/reviews"
|
|
21409
21566
|
},
|
|
21410
21567
|
requiredCreateFields: ["category", "state", "businessName", "websiteUrl", "tags", "idempotencyKey"],
|
|
21411
|
-
|
|
21568
|
+
optionalCreateFields: ["schemaOrgType"],
|
|
21569
|
+
systemCompiledSections: ["identity", "LocalBusiness JSON-LD", "services/products", "service areas/locations", "genuine media", "reviews and theme counts", "evidence", "answered FAQ", "collection receipts"],
|
|
21412
21570
|
workflow: ["list-local-sourcebook-tags", "get-local-sourcebook-contract", "prepare-local-sourcebook-write", "validate-local-sourcebook-write", "local-sourcebook-capture", "local_sourcebook_submission_status"],
|
|
21413
21571
|
ownership: "Only the MCP Scraper account that captures a listing can read it or request a paid evidence refresh.",
|
|
21572
|
+
billing: { validationCredits: 0, createCredits: 10, editCredits: 2, acquisitionIncluded: true, chargeEvent: "accepted_write" },
|
|
21414
21573
|
acquisition: { websitePagesMaximum: 100, reviewsMaximum: 500, exactMapsIdentityRequired: true, partialResultsRequireStoppingReasons: true },
|
|
21415
21574
|
publication: { automatic: true, evidenceCompiledOnly: true, ownerAuthoredClaimsAccepted: false, adminReviewRequired: false, lastPublishedRevisionRemainsPublicDuringRefresh: true },
|
|
21575
|
+
structuredData: localSourcebookSchemaContract(category),
|
|
21416
21576
|
tagPolicy: { inspectVocabularyFirst: true, minimum: 1, maximum: 20, newTagsRequire: ["central=true", "reusable=true", "description"], newTagsStartAs: "pending" }
|
|
21417
21577
|
};
|
|
21418
21578
|
}
|
|
@@ -21427,17 +21587,20 @@ async function prepareLocalSourcebookWrite(input) {
|
|
|
21427
21587
|
];
|
|
21428
21588
|
const tagResolutions = await resolveLocalSourcebookTags(candidates);
|
|
21429
21589
|
const publicUrls = localSourcebookPublicUrls(parsed.category, parsed.state, slug2);
|
|
21590
|
+
const schemaOrgType = parsed.schemaOrgType ?? defaultLocalSourcebookSchemaType(parsed.category);
|
|
21430
21591
|
return {
|
|
21431
21592
|
ok: true,
|
|
21432
21593
|
route: { category: parsed.category, state: parsed.state, slug: slug2, ...publicUrls },
|
|
21433
|
-
proposedWrite: { ...parsed, slug: slug2, tags: tagResolutions.filter((item) => item.action === "reuse").map((item) => item.tag) },
|
|
21594
|
+
proposedWrite: { ...parsed, schemaOrgType, slug: slug2, tags: tagResolutions.filter((item) => item.action === "reuse").map((item) => item.tag) },
|
|
21434
21595
|
contract: getLocalSourcebookContract(parsed.category),
|
|
21435
21596
|
tagResolutions,
|
|
21436
21597
|
instructions: [
|
|
21437
21598
|
"Resolve every review candidate before capture; do not silently create a near-duplicate tag.",
|
|
21438
21599
|
"Call validate-local-sourcebook-write with the proposed identity, resolved tags, and any required tag decisions.",
|
|
21600
|
+
"Keep the proposed schemaOrgType or choose a more specific accepted LocalBusiness subtype from the contract. Arbitrary JSON-LD is not accepted.",
|
|
21439
21601
|
"Call local-sourcebook-capture only after validation returns valid=true. Capture queues paid acquisition and automatically publishes the resulting system-compiled evidence revision.",
|
|
21440
|
-
"
|
|
21602
|
+
"Creating the listing costs 10 Credits total, including acquisition. Validation is free.",
|
|
21603
|
+
"Owners cannot supply public listing claims directly. A source-backed local_sourcebook_refresh costs 2 Credits."
|
|
21441
21604
|
]
|
|
21442
21605
|
};
|
|
21443
21606
|
}
|
|
@@ -21447,7 +21610,10 @@ async function validateLocalSourcebookWrite(input) {
|
|
|
21447
21610
|
let normalizedIdentity2;
|
|
21448
21611
|
const parsedIdentity = DirectoryIdentitySchema.safeParse(input.identity);
|
|
21449
21612
|
if (!parsedIdentity.success) errors.push(...parsedIdentity.error.issues.map((issue) => `identity.${issue.path.join(".")}: ${issue.message}`));
|
|
21450
|
-
else normalizedIdentity2 =
|
|
21613
|
+
else normalizedIdentity2 = {
|
|
21614
|
+
...parsedIdentity.data,
|
|
21615
|
+
schemaOrgType: parsedIdentity.data.schemaOrgType ?? defaultLocalSourcebookSchemaType(parsedIdentity.data.category)
|
|
21616
|
+
};
|
|
21451
21617
|
const candidates = input.tagCandidates ?? (normalizedIdentity2?.tags.map((tag) => ({ tag })) ?? []);
|
|
21452
21618
|
const resolutions = await resolveLocalSourcebookTags(candidates);
|
|
21453
21619
|
const decisions = new Map((input.tagDecisions ?? []).map((decision) => [normalizeTag(decision.tag), decision]));
|
|
@@ -21496,6 +21662,7 @@ var SubmitSchema = z26.object({
|
|
|
21496
21662
|
state: z26.string().trim().length(2).transform((value) => value.toLowerCase()),
|
|
21497
21663
|
businessName: z26.string().trim().min(2).max(160),
|
|
21498
21664
|
websiteUrl: z26.url().refine((value) => /^https?:\/\//.test(value), "websiteUrl must use http or https"),
|
|
21665
|
+
schemaOrgType: LocalSourcebookSchemaTypeSchema.optional(),
|
|
21499
21666
|
slug: z26.string().trim().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).max(100).optional(),
|
|
21500
21667
|
idempotencyKey: z26.string().trim().min(8).max(200).optional()
|
|
21501
21668
|
}).strict();
|
|
@@ -21506,6 +21673,18 @@ var ValidateWriteSchema = z26.object({
|
|
|
21506
21673
|
tagDecisions: z26.array(DirectoryTagDecisionSchema).max(20).optional()
|
|
21507
21674
|
}).strict();
|
|
21508
21675
|
var CaptureWriteSchema = ValidateWriteSchema.extend({ idempotencyKey: z26.string().trim().min(8).max(200).optional() });
|
|
21676
|
+
function withCanonicalLocalSourcebookUrls(listing) {
|
|
21677
|
+
const category = z26.enum(LOCAL_SOURCEBOOK_CATEGORIES).safeParse(listing.category);
|
|
21678
|
+
const state = typeof listing.state === "string" ? listing.state : "";
|
|
21679
|
+
const listingSlug = typeof listing.slug === "string" ? listing.slug : "";
|
|
21680
|
+
if (!category.success || !state || !listingSlug) return listing;
|
|
21681
|
+
const publicUrls = localSourcebookPublicUrls(category.data, state, listingSlug);
|
|
21682
|
+
return {
|
|
21683
|
+
...listing,
|
|
21684
|
+
canonicalPaths: { profile: publicUrls.profilePath, reviews: publicUrls.reviewsPath },
|
|
21685
|
+
canonicalUrls: { profile: publicUrls.profileUrl, reviews: publicUrls.reviewsUrl }
|
|
21686
|
+
};
|
|
21687
|
+
}
|
|
21509
21688
|
function acquisitionPlan(websiteUrl) {
|
|
21510
21689
|
return {
|
|
21511
21690
|
website: { startUrl: websiteUrl, mode: "broad_site_crawl", capture: ["homepage", "services", "products", "locations", "service_areas", "about", "team_staff", "contact", "policies"] },
|
|
@@ -21540,13 +21719,19 @@ localSourcebookApp.post("/capture", createApiKeyAuth(), async (c) => {
|
|
|
21540
21719
|
if (!parsed.success) return c.json({ error: "invalid_capture_request", issues: parsed.error.issues }, 400);
|
|
21541
21720
|
const validation = await validateLocalSourcebookWrite(parsed.data);
|
|
21542
21721
|
if (!validation.valid) return c.json({ error: "invalid_directory_write", ...validation }, 400);
|
|
21722
|
+
let billing = null;
|
|
21723
|
+
let persisted = false;
|
|
21543
21724
|
try {
|
|
21544
21725
|
const identity = validation.normalizedIdentity;
|
|
21545
|
-
|
|
21726
|
+
const idempotencyKey3 = parsed.data.idempotencyKey ?? identity.idempotencyKey;
|
|
21727
|
+
if (!idempotencyKey3) {
|
|
21546
21728
|
return c.json({ error: "idempotency_key_required", message: "A stable idempotencyKey is required for a new Local Sourcebook capture." }, 400);
|
|
21547
21729
|
}
|
|
21548
21730
|
const listingSlug = identity.slug ?? slug(identity.businessName);
|
|
21549
21731
|
const publicUrls = localSourcebookPublicUrls(identity.category, identity.state, listingSlug);
|
|
21732
|
+
const charged = await chargeListingWrite({ surface: "local_sourcebook", kind: "create", userId: Number(user.id), idempotencyKey: idempotencyKey3 });
|
|
21733
|
+
if ("insufficient" in charged) return c.json(charged.insufficient, 402);
|
|
21734
|
+
billing = charged;
|
|
21550
21735
|
const submission = await createLocalSourcebookSubmission({
|
|
21551
21736
|
ownerUserId: Number(user.id),
|
|
21552
21737
|
category: identity.category,
|
|
@@ -21554,7 +21739,9 @@ localSourcebookApp.post("/capture", createApiKeyAuth(), async (c) => {
|
|
|
21554
21739
|
slug: listingSlug,
|
|
21555
21740
|
businessName: identity.businessName,
|
|
21556
21741
|
websiteUrl: identity.websiteUrl,
|
|
21557
|
-
|
|
21742
|
+
schemaOrgType: identity.schemaOrgType,
|
|
21743
|
+
idempotencyKey: idempotencyKey3,
|
|
21744
|
+
billing: { kind: billing.kind, credits: billing.credits, amountMc: billing.amountMc, debitKey: billing.debitKey },
|
|
21558
21745
|
payload: {
|
|
21559
21746
|
id: "",
|
|
21560
21747
|
category: identity.category,
|
|
@@ -21562,6 +21749,7 @@ localSourcebookApp.post("/capture", createApiKeyAuth(), async (c) => {
|
|
|
21562
21749
|
slug: listingSlug,
|
|
21563
21750
|
name: identity.businessName,
|
|
21564
21751
|
websiteUrl: identity.websiteUrl,
|
|
21752
|
+
schemaOrgType: identity.schemaOrgType,
|
|
21565
21753
|
tags: validation.normalizedTags,
|
|
21566
21754
|
canonicalPaths: { profile: publicUrls.profilePath, reviews: publicUrls.reviewsPath },
|
|
21567
21755
|
canonicalUrls: { profile: publicUrls.profileUrl, reviews: publicUrls.reviewsUrl },
|
|
@@ -21572,10 +21760,12 @@ localSourcebookApp.post("/capture", createApiKeyAuth(), async (c) => {
|
|
|
21572
21760
|
reviews: []
|
|
21573
21761
|
}
|
|
21574
21762
|
});
|
|
21763
|
+
persisted = true;
|
|
21575
21764
|
await persistLocalSourcebookTags(submission.id, Number(user.id), validation.normalizedTags, parsed.data.tagDecisions);
|
|
21576
21765
|
const dispatched = await dispatchLocalSourcebookAcquisition(submission.id).catch(() => false);
|
|
21577
|
-
return c.json({ ok: true, valid: true, submission, publicUrls: localSourcebookPublicationLinks(submission), normalizedTags: validation.normalizedTags, acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(identity.websiteUrl) }, 202);
|
|
21766
|
+
return c.json({ ok: true, valid: true, submission, publicUrls: localSourcebookPublicationLinks(submission), normalizedTags: validation.normalizedTags, acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(identity.websiteUrl), billing: publicListingWriteBilling(billing) }, 202);
|
|
21578
21767
|
} catch (error) {
|
|
21768
|
+
if (billing && !persisted) await refundListingWrite(billing, "Local Sourcebook creation failed before persistence.").catch(() => null);
|
|
21579
21769
|
const message = error instanceof Error ? error.message : String(error);
|
|
21580
21770
|
const conflict = message.includes("revision conflict") ? "revision_conflict" : message.includes("idempotency key conflicts") ? "idempotency_conflict" : null;
|
|
21581
21771
|
return c.json({ error: conflict ?? "capture_failed", message }, conflict ? 409 : 400);
|
|
@@ -21587,13 +21777,22 @@ localSourcebookApp.post("/submissions", createApiKeyAuth(), async (c) => {
|
|
|
21587
21777
|
const parsed = SubmitSchema.safeParse(await c.req.json().catch(() => null));
|
|
21588
21778
|
if (!parsed.success) return c.json({ error: "invalid_submission", issues: parsed.error.issues }, 400);
|
|
21589
21779
|
const input = parsed.data;
|
|
21780
|
+
if (!input.idempotencyKey) return c.json({ error: "idempotency_key_required", message: "A stable idempotencyKey is required for a new Local Sourcebook listing." }, 400);
|
|
21590
21781
|
const listingSlug = input.slug ?? slug(input.businessName);
|
|
21782
|
+
const schemaOrgType = input.schemaOrgType ?? defaultLocalSourcebookSchemaType(input.category);
|
|
21591
21783
|
const publicUrls = localSourcebookPublicUrls(input.category, input.state, listingSlug);
|
|
21784
|
+
let billing = null;
|
|
21785
|
+
let persisted = false;
|
|
21592
21786
|
try {
|
|
21787
|
+
const charged = await chargeListingWrite({ surface: "local_sourcebook", kind: "create", userId: Number(user.id), idempotencyKey: input.idempotencyKey });
|
|
21788
|
+
if ("insufficient" in charged) return c.json(charged.insufficient, 402);
|
|
21789
|
+
billing = charged;
|
|
21593
21790
|
const submission = await createLocalSourcebookSubmission({
|
|
21594
21791
|
ownerUserId: Number(user.id),
|
|
21595
21792
|
...input,
|
|
21793
|
+
schemaOrgType,
|
|
21596
21794
|
slug: listingSlug,
|
|
21795
|
+
billing: { kind: billing.kind, credits: billing.credits, amountMc: billing.amountMc, debitKey: billing.debitKey },
|
|
21597
21796
|
payload: {
|
|
21598
21797
|
id: "",
|
|
21599
21798
|
category: input.category,
|
|
@@ -21601,6 +21800,7 @@ localSourcebookApp.post("/submissions", createApiKeyAuth(), async (c) => {
|
|
|
21601
21800
|
slug: listingSlug,
|
|
21602
21801
|
name: input.businessName,
|
|
21603
21802
|
websiteUrl: input.websiteUrl,
|
|
21803
|
+
schemaOrgType,
|
|
21604
21804
|
canonicalPaths: { profile: publicUrls.profilePath, reviews: publicUrls.reviewsPath },
|
|
21605
21805
|
canonicalUrls: { profile: publicUrls.profileUrl, reviews: publicUrls.reviewsUrl },
|
|
21606
21806
|
verificationState: "submitted",
|
|
@@ -21610,9 +21810,11 @@ localSourcebookApp.post("/submissions", createApiKeyAuth(), async (c) => {
|
|
|
21610
21810
|
reviews: []
|
|
21611
21811
|
}
|
|
21612
21812
|
});
|
|
21813
|
+
persisted = true;
|
|
21613
21814
|
const dispatched = await dispatchLocalSourcebookAcquisition(submission.id).catch(() => false);
|
|
21614
|
-
return c.json({ ok: true, submission, publicUrls: localSourcebookPublicationLinks(submission), acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(input.websiteUrl) }, 202);
|
|
21815
|
+
return c.json({ ok: true, submission, publicUrls: localSourcebookPublicationLinks(submission), acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(input.websiteUrl), billing: publicListingWriteBilling(billing) }, 202);
|
|
21615
21816
|
} catch (error) {
|
|
21817
|
+
if (billing && !persisted) await refundListingWrite(billing, "Local Sourcebook creation failed before persistence.").catch(() => null);
|
|
21616
21818
|
return c.json({ error: "submission_conflict", message: error instanceof Error ? error.message : String(error) }, 409);
|
|
21617
21819
|
}
|
|
21618
21820
|
});
|
|
@@ -21629,22 +21831,45 @@ localSourcebookApp.on(["PATCH", "POST"], "/submissions/:id/draft", createApiKeyA
|
|
|
21629
21831
|
localSourcebookApp.post("/submissions/:id/refresh", createApiKeyAuth(), async (c) => {
|
|
21630
21832
|
const user = c.get("user");
|
|
21631
21833
|
if (!user.subscription_tier) return c.json({ error: "subscription_required" }, 402);
|
|
21834
|
+
const userId = Number(user.id);
|
|
21835
|
+
const existing = await getLocalSourcebookSubmission(c.req.param("id"), userId);
|
|
21836
|
+
if (!existing) return c.json({ error: "not_found" }, 404);
|
|
21837
|
+
if (existing.status === "rejected" || existing.status === "unpublished") {
|
|
21838
|
+
return c.json({ error: "moderated_listing", message: `A ${existing.status} Local Sourcebook listing cannot be refreshed by its owner.` }, 409);
|
|
21839
|
+
}
|
|
21840
|
+
if (existing.status === "queued" || existing.status === "enriching") {
|
|
21841
|
+
return c.json({ error: "acquisition_in_progress", message: "This listing already has an acquisition in progress." }, 409);
|
|
21842
|
+
}
|
|
21843
|
+
const idempotencyKey3 = `refresh:${existing.id}:revision:${existing.draftRevision}`;
|
|
21844
|
+
let billing = null;
|
|
21632
21845
|
try {
|
|
21633
|
-
const
|
|
21634
|
-
if (
|
|
21846
|
+
const charged = await chargeListingWrite({ surface: "local_sourcebook", kind: "edit", userId, idempotencyKey: idempotencyKey3 });
|
|
21847
|
+
if ("insufficient" in charged) return c.json(charged.insufficient, 402);
|
|
21848
|
+
billing = charged;
|
|
21849
|
+
const submission = await queueLocalSourcebookRefresh(c.req.param("id"), userId, {
|
|
21850
|
+
kind: billing.kind,
|
|
21851
|
+
credits: billing.credits,
|
|
21852
|
+
amountMc: billing.amountMc,
|
|
21853
|
+
debitKey: billing.debitKey
|
|
21854
|
+
});
|
|
21855
|
+
if (!submission) {
|
|
21856
|
+
await refundListingWrite(billing, "Local Sourcebook listing was not found before refresh persistence.").catch(() => null);
|
|
21857
|
+
return c.json({ error: "not_found" }, 404);
|
|
21858
|
+
}
|
|
21635
21859
|
const dispatched = await dispatchLocalSourcebookAcquisition(submission.id).catch(() => false);
|
|
21636
|
-
return c.json({ ok: true, submission, publicUrls: localSourcebookPublicationLinks(submission), acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(submission.websiteUrl) }, 202);
|
|
21860
|
+
return c.json({ ok: true, submission, publicUrls: localSourcebookPublicationLinks(submission), acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(submission.websiteUrl), billing: publicListingWriteBilling(billing) }, 202);
|
|
21637
21861
|
} catch (error) {
|
|
21862
|
+
if (billing) await refundListingWrite(billing, "Local Sourcebook refresh failed before persistence.").catch(() => null);
|
|
21638
21863
|
return c.json({ error: "moderated_listing", message: error instanceof Error ? error.message : String(error) }, 409);
|
|
21639
21864
|
}
|
|
21640
21865
|
});
|
|
21641
21866
|
var publicLocalSourcebookApp = new Hono17();
|
|
21642
|
-
publicLocalSourcebookApp.get("/", async (c) => c.json({ ok: true, listings: await listAllPublicLocalSourcebook() }));
|
|
21643
|
-
publicLocalSourcebookApp.get("/:category", async (c) => c.json({ ok: true, listings: await listAllPublicLocalSourcebook(c.req.param("category")) }));
|
|
21644
|
-
publicLocalSourcebookApp.get("/:category/:state", async (c) => c.json({ ok: true, listings: await listPublicLocalSourcebook(c.req.param("category"), c.req.param("state").toLowerCase()) }));
|
|
21867
|
+
publicLocalSourcebookApp.get("/", async (c) => c.json({ ok: true, listings: (await listAllPublicLocalSourcebook()).map(withCanonicalLocalSourcebookUrls) }));
|
|
21868
|
+
publicLocalSourcebookApp.get("/:category", async (c) => c.json({ ok: true, listings: (await listAllPublicLocalSourcebook(c.req.param("category"))).map(withCanonicalLocalSourcebookUrls) }));
|
|
21869
|
+
publicLocalSourcebookApp.get("/:category/:state", async (c) => c.json({ ok: true, listings: (await listPublicLocalSourcebook(c.req.param("category"), c.req.param("state").toLowerCase())).map(withCanonicalLocalSourcebookUrls) }));
|
|
21645
21870
|
publicLocalSourcebookApp.get("/:category/:state/:slug", async (c) => {
|
|
21646
21871
|
const listing = await getPublicLocalSourcebook(c.req.param("category"), c.req.param("state").toLowerCase(), c.req.param("slug"));
|
|
21647
|
-
return listing ? c.json({ ok: true, listing }) : c.json({ error: "not_found" }, 404);
|
|
21872
|
+
return listing ? c.json({ ok: true, listing: withCanonicalLocalSourcebookUrls(listing) }) : c.json({ error: "not_found" }, 404);
|
|
21648
21873
|
});
|
|
21649
21874
|
var adminLocalSourcebookApp = new Hono17();
|
|
21650
21875
|
adminLocalSourcebookApp.use("*", adminAuth);
|
|
@@ -31006,6 +31231,12 @@ async function prepareCommonsEntity(input, user) {
|
|
|
31006
31231
|
writePath: "commons_prepare_entity -> commons_validate_entity -> commons_submit_entity"
|
|
31007
31232
|
},
|
|
31008
31233
|
contract,
|
|
31234
|
+
billing: {
|
|
31235
|
+
validationCredits: LISTING_WRITE_RATE_POLICY.validationCredits,
|
|
31236
|
+
createCredits: LISTING_WRITE_RATE_POLICY.create.credits,
|
|
31237
|
+
editCredits: LISTING_WRITE_RATE_POLICY.edit.credits,
|
|
31238
|
+
chargeEvent: LISTING_WRITE_RATE_POLICY.chargeEvent
|
|
31239
|
+
},
|
|
31009
31240
|
duplicateCandidates,
|
|
31010
31241
|
tagResolutions,
|
|
31011
31242
|
headingPlan: {
|
|
@@ -31020,7 +31251,8 @@ async function prepareCommonsEntity(input, user) {
|
|
|
31020
31251
|
"Use the returned profile as a contract, not a rigid empty template: do not include History, Reception, Pricing, Timeline, or similar sections unless source evidence supports them.",
|
|
31021
31252
|
"Write neutral encyclopedia prose. Do not paste raw scrape Markdown, marketing layout fragments, or empty/filler headings.",
|
|
31022
31253
|
"Attach a featured image for auto-publish eligibility, preserve original URL/source byline/relCanonical when republishing, and put media assets in the media array.",
|
|
31023
|
-
"Call commons_validate_entity with the composed payload before commons_submit_entity."
|
|
31254
|
+
"Call commons_validate_entity with the composed payload before commons_submit_entity.",
|
|
31255
|
+
"Validation is free. Submitting a new entity costs 10 Credits; submitting an edit to an existing entity costs 2 Credits."
|
|
31024
31256
|
]
|
|
31025
31257
|
};
|
|
31026
31258
|
}
|
|
@@ -31109,6 +31341,14 @@ async function getCommonsEntity(idOrSlug) {
|
|
|
31109
31341
|
});
|
|
31110
31342
|
return result.rows[0] ? rowToEntity(result.rows[0]) : null;
|
|
31111
31343
|
}
|
|
31344
|
+
async function classifyCommonsWrite(input, user) {
|
|
31345
|
+
await ensureCommonsSchema();
|
|
31346
|
+
const normalized = normalizeSubmitInput(input);
|
|
31347
|
+
const existingProposal = await findProposalByIdempotency(user.id, normalized.idempotencyKey);
|
|
31348
|
+
if (existingProposal) return existingProposal.proposalType === "edit_entity" ? "edit" : "create";
|
|
31349
|
+
const existing = await findEntityBySlugOrIdAnyStatus(normalized.slug, normalized.entityId);
|
|
31350
|
+
return existing ? "edit" : "create";
|
|
31351
|
+
}
|
|
31112
31352
|
async function submitCommonsEntity(input, user) {
|
|
31113
31353
|
await ensureCommonsSchema();
|
|
31114
31354
|
const normalized = normalizeSubmitInput(input);
|
|
@@ -31373,7 +31613,8 @@ function commonsDatabaseReport() {
|
|
|
31373
31613
|
normalWorkflow: ["commons_prepare_entity", "commons_validate_entity", "commons_submit_entity"],
|
|
31374
31614
|
writesCreate: ["commons_proposals", "commons_ledger", "commons_entities when accepted"],
|
|
31375
31615
|
safeAutoPublish: "New non-conflicting entities with title, description, featured image, and body/source evidence.",
|
|
31376
|
-
conflictBehavior: "Existing entity edits require baseRevision. Otherwise the proposal is pending_review."
|
|
31616
|
+
conflictBehavior: "Existing entity edits require baseRevision. Otherwise the proposal is pending_review.",
|
|
31617
|
+
billing: { validationCredits: 0, createCredits: LISTING_WRITE_RATE_POLICY.create.credits, editCredits: LISTING_WRITE_RATE_POLICY.edit.credits }
|
|
31377
31618
|
},
|
|
31378
31619
|
searchCorpus: ["title", "disambiguationName", "description", "tags", "keywords", "relatedEntities", "bodyMarkdown", "sections", "media", "source metadata", "jsonLd"],
|
|
31379
31620
|
indexing: commonsIndexingPolicy(),
|
|
@@ -32664,10 +32905,22 @@ commonsApp.post("/entities/validate", auth2, async (c) => {
|
|
|
32664
32905
|
commonsApp.post("/entities/propose", auth2, async (c) => {
|
|
32665
32906
|
const parsed = SubmitEntitySchema.safeParse(await c.req.json().catch(() => ({})));
|
|
32666
32907
|
if (!parsed.success) return validationError(c, parsed.error);
|
|
32908
|
+
let billing = null;
|
|
32667
32909
|
try {
|
|
32910
|
+
const user = c.get("user");
|
|
32911
|
+
const kind = await classifyCommonsWrite(parsed.data, user);
|
|
32912
|
+
const charged = await chargeListingWrite({
|
|
32913
|
+
surface: "transparent_commons",
|
|
32914
|
+
kind,
|
|
32915
|
+
userId: Number(user.id),
|
|
32916
|
+
idempotencyKey: parsed.data.idempotencyKey
|
|
32917
|
+
});
|
|
32918
|
+
if ("insufficient" in charged) return c.json(charged.insufficient, 402);
|
|
32919
|
+
billing = charged;
|
|
32668
32920
|
const result = await submitCommonsEntity(parsed.data, c.get("user"));
|
|
32669
|
-
return c.json({ ok: true, data: result }, result.applied ? 201 : 202);
|
|
32921
|
+
return c.json({ ok: true, data: result, billing: publicListingWriteBilling(billing) }, result.applied ? 201 : 202);
|
|
32670
32922
|
} catch (error) {
|
|
32923
|
+
if (billing) await refundListingWrite(billing, "Transparent Commons write failed before persistence.").catch(() => null);
|
|
32671
32924
|
if (error instanceof CommonsRepositoryError) {
|
|
32672
32925
|
return c.json({ ok: false, error: error.code, message: error.message }, repositoryStatus(error));
|
|
32673
32926
|
}
|
|
@@ -40751,7 +41004,7 @@ app.get("/cron/tick", async (c) => {
|
|
|
40751
41004
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
40752
41005
|
return c.json({ error: "Unauthorized" }, 401);
|
|
40753
41006
|
}
|
|
40754
|
-
const { drainQueue } = await import("./worker-
|
|
41007
|
+
const { drainQueue } = await import("./worker-4LZGRHDA.js");
|
|
40755
41008
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
40756
41009
|
const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
|
|
40757
41010
|
const [results, sweepResult, reapResult, expiredResult, blobCleanup, connectedDataArtifactCleanup, connectedAccountBilling, directoryWorkflowDispatch, directoryWorkflowReconciliation, directoryArtifactCleanup, siteExtractArtifactCleanup, siteExtractRedispatch, siteExtractReconciliation, paaHarvestRedispatch, paaHarvestReconciliation, localSourcebookRedispatch] = await Promise.all([
|
|
@@ -40806,8 +41059,8 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
|
|
|
40806
41059
|
finishExtractJob: finishExtractJob2,
|
|
40807
41060
|
terminalExtractJobStatus: terminalExtractJobStatus2,
|
|
40808
41061
|
extractJobLimitInfo: extractJobLimitInfo2
|
|
40809
|
-
} = await import("./site-extract-repository-
|
|
40810
|
-
const { assembleExtractArtifacts } = await import("./extract-bundle-
|
|
41062
|
+
} = await import("./site-extract-repository-G723PZIL.js");
|
|
41063
|
+
const { assembleExtractArtifacts } = await import("./extract-bundle-DYMQRQWG.js");
|
|
40811
41064
|
const existing = await getExtractJob2(jobId2);
|
|
40812
41065
|
if (!existing) return c.json({ error: "job not found" }, 404);
|
|
40813
41066
|
if (existing.status !== "failed") {
|
|
@@ -40834,7 +41087,7 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
|
|
|
40834
41087
|
if (!updated) return c.json({ error: "job state changed during re-finalization" }, 409);
|
|
40835
41088
|
let settlement = "already_settled_or_refunded";
|
|
40836
41089
|
if (job.billedMc == null && job.userId != null) {
|
|
40837
|
-
const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-
|
|
41090
|
+
const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-G723PZIL.js");
|
|
40838
41091
|
const heldMc = Number(job.options.heldMc ?? 0);
|
|
40839
41092
|
const successful = await countSuccessfulPages2(jobId2);
|
|
40840
41093
|
const usedMc = Math.min(successful * MC_COSTS.page_scrape, heldMc);
|
|
@@ -41018,4 +41271,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
41018
41271
|
export {
|
|
41019
41272
|
app
|
|
41020
41273
|
};
|
|
41021
|
-
//# sourceMappingURL=server-
|
|
41274
|
+
//# sourceMappingURL=server-7B75SDNG.js.map
|